feat: implement smart notebook & tag apply, undo, and empty notebook cleanup#26
feat: implement smart notebook & tag apply, undo, and empty notebook cleanup#26Harsh16gupta wants to merge 5 commits into
Conversation
There was a problem hiding this comment.
Pull request overview
This PR adds an “application layer” to the plugin so users can apply categorization results (move notes + apply semantic tags), undo the last apply via a persisted change log, and optionally clean up original notebooks that became empty. It also adds corresponding UI controls and plugin-side settings to support these operations.
Changes:
- Adds apply/undo/cleanup commands and a persisted
categorization.changeLogstate to support revert and cleanup workflows. - Extends the webview state machine and UI (Dashboard + History) to trigger and monitor apply/undo/cleanup progress and results.
- Introduces new message types (
apply,undo,cleanUpEmptyNotebooks, settings get/update) to coordinate between webview and plugin.
Reviewed changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated 7 comments.
Show a summary per file
| File | Description |
|---|---|
| src/webview/panel.css | Adds styling for Apply/Undo/Cleanup UI controls and status banners. |
| src/webview/pages/HistoryPage.tsx | Implements a History view that surfaces undo/cleanup actions and status based on stored change log. |
| src/webview/pages/DashboardPage.tsx | Adds “Apply categorization” UI + wires up apply state/progress/error handling. |
| src/webview/context/AppStateContext.tsx | Adds settings fetch/update and apply/undo/cleanup state + polling handling in the app state context. |
| src/types/panel.ts | Adds ApplyOptions, ApplyMessage, and new panel message types for apply/undo/cleanup + settings. |
| src/index.ts | Registers plugin settings and routes new webview messages to apply/undo/cleanup background operations with a lock guard. |
| src/commands/applyChanges.ts | New core implementation for apply, undo, and cleanup (tag matching, moving notes, change log persistence). |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| const folderMap: { [clusterId: number]: string } = {}; | ||
| const createdFolderIds: string[] = []; | ||
| let uncategorizedFolderId = ''; | ||
| if (options.method === 'notebooks' || options.method === 'both') { | ||
| for (const clusterId of uniqueClusterIds) { | ||
| const clusterName = clusterNames[clusterId] || `Cluster ${clusterId + 1}`; | ||
| const { id: childFolderId, created } = await getOrCreateFolderOptimized(clusterName); | ||
| folderMap[clusterId] = childFolderId; | ||
| if (created) { | ||
| createdFolderIds.push(childFolderId); | ||
| } | ||
| } | ||
|
|
||
| if (assignments.includes(-1)) { | ||
| const { id: noiseFolderId, created } = await getOrCreateFolderOptimized('Uncategorized'); | ||
| uncategorizedFolderId = noiseFolderId; | ||
| if (created) { | ||
| createdFolderIds.push(noiseFolderId); | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
@HahaBill, I have a quick question about the folder structure when applying categorization.
Right now, when the user clicks Apply, the plugin creates cluster folders (like "Machine Learning", "Web Development") directly at the root level of the notebook list. So they just sit alongside all the user's existing notebooks.
The other option is to nest them under a single parent folder, something like:

This keeps things cleaner (I don't like this approach as it creates an extra level of nesting).
Which approach do you think works better? Let me know what you think!
There was a problem hiding this comment.
@Harsh16gupta Personally, I think not having AI Categorized Notes and put them all at the root level is cleaner and better UX.
I would try to think of a smooth UI/UX strategy to make users know that those folders were AI-generated. If that is too ambitious then putting them at the root level suffice for now.
There was a problem hiding this comment.
@Harsh16gupta Personally, I think not having
AI Categorized Notesand put them all at the root level is cleaner and better UX.
Totally agree with putting them at the root level!
I would try to think of a smooth UI/UX strategy to make users know that those folders were AI-generated. If that is too ambitious then putting them at the root level suffice for now.
Regarding this,
I don't think we can do that from this plugin repo.
- The most we can do is add a small tag or symbol (for example,
ai-in front of the notebook names).
Doing anything in the UI to let users know would require some changes in the main Joplin repo. Maybe we can leave it like this for now, and then in the weekly updates, I can ask Laurent about this.
There was a problem hiding this comment.
Let's ask Laurent if adding tags to notebooks is something that can be done, this way this is on. Otherwise, we can leave it as it is. At most what you can do is to add an 🤖 to notebooks to indicate that it was generate by the AI.
I'm opposed of adding ai- in front of the notebook names, it doesn't look nice.
There was a problem hiding this comment.
@laurent22 Hi Laurent, I feel like it's better if I include you in this conversation for you to have the direct context. What do you think about this?
|
All the review comments have been addressed except the one about the parent notebook (#26 (comment)). |
style: ui refinement, clean stylesheet structure, and visual polish
Overview
This PR implements the note categorization application layer, allowing users to apply folder organizations and smart semantic tags to notes in one click, revert changes (Undo), and clean up empty original notebook folders.
Key Features
Performance & Safety Optimizations
$O(1)$Map lookups.Screen Recording:
Screencast.From.2026-07-07.17-53-33.mp4