Skip to content

Folder duplicate: needs a server-side recursive copy endpoint #1122

Description

@Rasbandit

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:

  1. enumerate every descendant folder (walk /api/folders by path prefix),
  2. fetch each folder's note list,
  3. create the destination folder tree,
  4. 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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions