Move a folder by dragging it #40

Closed
opened 2026-08-25 19:36:23 +00:00 by jcoffey-dev · 0 comments
Owner

Reparenting a folder meant the Folders settings page, or nothing at all. The tree already accepted messages dropped onto a folder, so folders now travel the same way: drag one onto another to nest it, or onto the Folders heading to bring it back to the top level.

Behaviour

  • The heading reads "Drop here for the top level" while a folder is in flight — an unlabelled strip of heading is not a discoverable target.
  • The dragged row fades, the row under the pointer is outlined, and only rows that would actually accept the drop light up.
  • After a move the destination expands, so the folder is visible where it landed rather than hidden inside a closed parent.
  • The move goes through updateMailbox, so the filter rules pointing at that folder follow it — the reconciliation from #37/#39 applies to drags for free.

Four drops are refused

A folder onto itself; into its own subtree; onto the parent it already has; and any folder the server gave a role, which isn't draggable at all. The subtree case is the one that matters, since it would orphan the branch — and it checks the whole subtree, not just immediate children.

One wrinkle worth recording: whether a drop is legal has to be known during dragover, when dataTransfer.getData is blocked for security. So the tree remembers what is being dragged rather than asking the drag.

Verified

The guards are pure and live in lib/folderMove.ts with 9 tests — role folders, whole-subtree detection, self-drop, drop-on-current-parent, root in both directions, and a missing target.

Driven in the real app against the mock:

  • draggable rows are exactly Newsletters and Work; Inbox, Archive, Drafts, Junk Mail, Sent and Trash all report draggable=false
  • dragging Newsletters onto Work nests it, and Work expands to show it
  • dragging it onto the Folders heading returns it to the top level
  • dropping Work onto itself: not a target, no highlight

Regression-checked the message drop, which shares the handler I changed: dragging a message onto a folder still carries application/x-ihasmail-emails and still highlights the folder, behaving identically with and without this change. (My synthetic drop does not complete the move in either case — the list count is unchanged on main too — so that is a limit of the harness, not of the feature.)

npm run typecheck, npm test — 222 web + 88 server passing.

🤖 Generated with Claude Code

Merged 2026-08-25 as coffey-labs/ihasmail@145abef3fe

Rebuilt from: git history, session transcript.

Reparenting a folder meant the Folders settings page, or nothing at all. The tree already accepted messages dropped onto a folder, so folders now travel the same way: **drag one onto another to nest it, or onto the Folders heading to bring it back to the top level.** ## Behaviour - The heading reads **"Drop here for the top level"** while a folder is in flight — an unlabelled strip of heading is not a discoverable target. - The dragged row fades, the row under the pointer is outlined, and only rows that would actually accept the drop light up. - After a move the destination expands, so the folder is visible where it landed rather than hidden inside a closed parent. - The move goes through `updateMailbox`, so the filter rules pointing at that folder follow it — the reconciliation from #37/#39 applies to drags for free. ## Four drops are refused A folder onto itself; into its own subtree; onto the parent it already has; and any folder the server gave a role, which isn't draggable at all. The subtree case is the one that matters, since it would orphan the branch — and it checks the **whole** subtree, not just immediate children. One wrinkle worth recording: whether a drop is legal has to be known during `dragover`, when `dataTransfer.getData` is blocked for security. So the tree remembers what is being dragged rather than asking the drag. ## Verified The guards are pure and live in `lib/folderMove.ts` with 9 tests — role folders, whole-subtree detection, self-drop, drop-on-current-parent, root in both directions, and a missing target. Driven in the real app against the mock: - draggable rows are exactly `Newsletters` and `Work`; `Inbox`, `Archive`, `Drafts`, `Junk Mail`, `Sent` and `Trash` all report `draggable=false` - dragging `Newsletters` onto `Work` nests it, and `Work` expands to show it - dragging it onto the Folders heading returns it to the top level - dropping `Work` onto itself: not a target, no highlight **Regression-checked the message drop**, which shares the handler I changed: dragging a message onto a folder still carries `application/x-ihasmail-emails` and still highlights the folder, behaving identically with and without this change. (My synthetic drop does not complete the move in either case — the list count is unchanged on main too — so that is a limit of the harness, not of the feature.) `npm run typecheck`, `npm test` — 222 web + 88 server passing. 🤖 Generated with [Claude Code](https://claude.com/claude-code) **Merged** 2026-08-25 as coffey-labs/ihasmail@145abef3fe02 <sub>Rebuilt from: git history, session transcript.</sub>
This repo is archived. You cannot comment on issues.