Duplicating a folder is the one action missing from the folder context menu (added in #1121). It was deliberately left out rather than shipped fragile.
Why it isn't just a frontend change
useDuplicateNote is a single-note operation: GET the source, then one crdt_create_with_content for the copy. There is no folder-copy endpoint, so duplicating a folder client-side would mean:
- enumerate every descendant folder (walk
/api/folders by path prefix),
- fetch each folder's note list,
- create the destination folder tree,
- duplicate every note individually.
That's N round trips with no atomicity. A failure partway leaves a half-copied folder in the vault — a bad state for a knowledge base, and one the user then has to clean up by hand. Retrying isn't safe either, since the already-copied notes would collide.
Proposed
A server-side endpoint that performs the recursive copy in one transaction, e.g.:
POST /folders/duplicate { src_path, new_path }
Server-side it can reuse the existing name-collision policy (collideBump's "<name> 1" shape) for the destination folder name, and mint note ids the same way the CRDT create path does so the copies are real live docs rather than detached rows.
Frontend follow-up once it exists
- Add
{ id: "duplicate", label: "Duplicate" } back to FOLDER_ACTIONS in frontend/src/viewer/tree-actions/action-list.ts (the icon is already mapped in ACTION_ICONS).
- Handle it in
handleActionPick in frontend/src/viewer/folder-tree.tsx, alongside the existing note-duplicate case.
- Derived folders (
syn: ids) have no backend id, so this must be path-addressed like rename/delete/move — see the path-vs-id split in partition().
Acceptance
- Duplicating a folder copies every descendant note and subfolder, or nothing at all.
- The copy's name follows the existing collision policy.
- Works for derived folders (those
/api/folders returns with a null id), not just explicit markers.
🤖 Generated with Claude Code
https://claude.ai/code/session_013fXPYtAKeUBLKjW9QmVRpZ
Duplicating a folder is the one action missing from the folder context menu (added in #1121). It was deliberately left out rather than shipped fragile.
Why it isn't just a frontend change
useDuplicateNoteis a single-note operation:GETthe source, then onecrdt_create_with_contentfor the copy. There is no folder-copy endpoint, so duplicating a folder client-side would mean:/api/foldersby path prefix),That's N round trips with no atomicity. A failure partway leaves a half-copied folder in the vault — a bad state for a knowledge base, and one the user then has to clean up by hand. Retrying isn't safe either, since the already-copied notes would collide.
Proposed
A server-side endpoint that performs the recursive copy in one transaction, e.g.:
Server-side it can reuse the existing name-collision policy (
collideBump's"<name> 1"shape) for the destination folder name, and mint note ids the same way the CRDT create path does so the copies are real live docs rather than detached rows.Frontend follow-up once it exists
{ id: "duplicate", label: "Duplicate" }back toFOLDER_ACTIONSinfrontend/src/viewer/tree-actions/action-list.ts(the icon is already mapped inACTION_ICONS).handleActionPickinfrontend/src/viewer/folder-tree.tsx, alongside the existing note-duplicate case.syn:ids) have no backend id, so this must be path-addressed like rename/delete/move — see the path-vs-id split inpartition().Acceptance
/api/foldersreturns with a null id), not just explicit markers.🤖 Generated with Claude Code
https://claude.ai/code/session_013fXPYtAKeUBLKjW9QmVRpZ