A folder tree, and dragging things into it #92

Closed
opened 2026-08-27 16:20:23 +00:00 by jcoffey-dev · 0 comments
Owner

Stacked on #91 — it uses the share dialog and badge from there. Review that one first; this targets share-files so the diff stays honest.

Files had a breadcrumb and a Move to… dialog. Moving anything meant opening a dialog and walking down to the folder you wanted, and there was nowhere to see the shape of the account at all.

There's now a folder tree in the sidebar, beside the mailbox tree it borrows its look from. Rows in the list and folders in the tree drag onto any folder in either pane, and folders dropped from outside upload with their structure intact.

Already existed, and I left alone: right-click menus on list rows, and drag-and-drop upload of loose files.

The tree loads in one query

filter: { nodeType: "directory" } returns every folder in the account — checked against 0.16.19 — so nothing waits on an expand and a drag knows every folder it could land on, including ones nobody has opened.

It's deliberately its own request. A filter Stalwart refuses fails with a request-level 400 that takes every method call in the request with it ({ parentId: null } does exactly that), so a per-level query batched alongside the listing would blank the whole view rather than just the sidebar.

Two things writing this turned up

The mock ignored the nodeType filter the live server applies. The tree asked for directories, was handed files too, and drew them as folders you could open into nothing. The mock now filters the way 0.16.19 does — and the store filters again on the way in, because a tree that believes whatever a server sends is a tree that draws files as folders on the next server that gets this wrong.

The drag state was per-pane, which cannot work. A drag starting in the list has to be recognised by the tree, and the pane that didn't start it never lit up or accepted the drop. Dropping still worked, because the drop handler re-checks from the drag itself — which is exactly why this would have shipped looking fine and been unusable. It's in the store now, with the reason written down.

Folder uploads

webkitGetAsEntry — non-standard in name, universal in practice. Its readEntries returns up to some entries per call and signals the end with an empty array, so a single read loses everything past the first batch. Both bounds in dropUpload.ts (depth, and entries per directory) are there because a directory tree from outside the app isn't something to take on trust; the test covering the second one found the version without it looping forever.

Verified

Against the mock: a row dragged onto a folder in the tree lights the target, is accepted, and moves it on the server (parentId null → f1); a top-level folder dragged to All files is refused as the no-op it is; the tree's own menu creates, renames, shares and deletes; the tree lists folders only.

npm run typecheck clean; 314 web and 77 server tests pass, 17 of them new.

🤖 Generated with Claude Code

Merged 2026-08-27 as coffey-labs/ihasmail@c9531c577c

Rebuilt from: git history, session transcript.

Stacked on #91 — it uses the share dialog and badge from there. Review that one first; this targets `share-files` so the diff stays honest. Files had a breadcrumb and a Move to… dialog. Moving anything meant opening a dialog and walking down to the folder you wanted, and there was nowhere to see the shape of the account at all. There's now a folder tree in the sidebar, beside the mailbox tree it borrows its look from. Rows in the list and folders in the tree drag onto any folder in either pane, and folders dropped from outside upload with their structure intact. **Already existed, and I left alone:** right-click menus on list rows, and drag-and-drop upload of loose files. ### The tree loads in one query `filter: { nodeType: "directory" }` returns every folder in the account — checked against 0.16.19 — so nothing waits on an expand and a drag knows every folder it could land on, including ones nobody has opened. It's deliberately its own request. A filter Stalwart refuses fails with a **request-level 400 that takes every method call in the request with it** (`{ parentId: null }` does exactly that), so a per-level query batched alongside the listing would blank the whole view rather than just the sidebar. ### Two things writing this turned up **The mock ignored the `nodeType` filter the live server applies.** The tree asked for directories, was handed files too, and drew them as folders you could open into nothing. The mock now filters the way 0.16.19 does — and the store filters again on the way in, because a tree that believes whatever a server sends is a tree that draws files as folders on the next server that gets this wrong. **The drag state was per-pane, which cannot work.** A drag starting in the list has to be recognised by the tree, and the pane that didn't start it never lit up or accepted the drop. Dropping still *worked*, because the drop handler re-checks from the drag itself — which is exactly why this would have shipped looking fine and been unusable. It's in the store now, with the reason written down. ### Folder uploads `webkitGetAsEntry` — non-standard in name, universal in practice. Its `readEntries` returns *up to* some entries per call and signals the end with an empty array, so a single read loses everything past the first batch. Both bounds in `dropUpload.ts` (depth, and entries per directory) are there because a directory tree from outside the app isn't something to take on trust; the test covering the second one found the version without it looping forever. ### Verified Against the mock: a row dragged onto a folder in the tree lights the target, is accepted, and moves it on the server (`parentId` null → `f1`); a top-level folder dragged to All files is refused as the no-op it is; the tree's own menu creates, renames, shares and deletes; the tree lists folders only. `npm run typecheck` clean; 314 web and 77 server tests pass, 17 of them new. 🤖 Generated with [Claude Code](https://claude.com/claude-code) **Merged** 2026-08-27 as coffey-labs/ihasmail@c9531c577ce1 <sub>Rebuilt from: git history, session transcript.</sub>
This repo is archived. You cannot comment on issues.