Moving or deleting five files meant doing it five times, each with its own confirm.
Rows now select the way a file manager taught everyone:
gesture
result
click
replaces the selection
ctrl / cmd click
adds or removes one
shift click
the run from the last row clicked
click past the last row, or Escape
clears it
Two or more selected raises a bar with Move to… and Delete; the row menu offers the same for the whole selection.
The parts that are decisions, not plumbing
One FileNode/set, not a loop. Not only for the round trip: a loop would apply half the moves and then throw, leaving a selection split across two folders with nothing saying which half went. One call is one answer, and notUpdated names whatever the server refused.
Right-clicking outside the selection moves it. Inside, the menu acts on all of it. Outside means you meant that row — so the selection follows the pointer rather than the menu quietly applying to something scrolled off-screen.
A drag carries the whole selection, which is why the payload is a list now (readDraggedIds), and why canDropFileNodes refuses unless every file in it can land: a drag that moves four of five and silently skips the fifth is worse than one that will not start.
A selection belongs to the folder it was made in. Changing folder or account drops it — rows left selected off-screen make the delete two folders later a surprise.
Verified
Mock stack, visible Chrome tab, four uploaded files:
result
plain click
one row; DOM checked, not just the screenshot
ctrl-click
two non-adjacent rows, bar says "2 items selected"
shift-click
the run a–c, "3 items selected"
Move to… on 3
"Move 3 items", Move here disabled at their current parent, all three moved in one action, selection cleared
Delete on 3
"Delete 3 items?", all three gone, one toast
click empty space
selection cleared (this was missing; found while testing and fixed)
folder change
selection cleared
The drag gesture itself is not hand-verified — HTML5 drag-and-drop does not respond to synthetic mouse events. Its two new pure pieces are unit-tested instead: readDraggedIds (one id, many ids, and a drag carrying nothing of ours) and canDropFileNodes (all-or-nothing, a folder into itself, the empty list, the top level).
typecheck clean, 603 web + 110 server tests, i18n:check clean, build clean.
Moving or deleting five files meant doing it five times, each with its own confirm.
Rows now select the way a file manager taught everyone:
| gesture | result |
|---|---|
| click | replaces the selection |
| ctrl / cmd click | adds or removes one |
| shift click | the run from the last row clicked |
| click past the last row, or Escape | clears it |
Two or more selected raises a bar with **Move to…** and **Delete**; the row menu offers the same for the whole selection.
## The parts that are decisions, not plumbing
**One `FileNode/set`, not a loop.** Not only for the round trip: a loop would apply half the moves and then throw, leaving a selection split across two folders with nothing saying which half went. One call is one answer, and `notUpdated` names whatever the server refused.
**Right-clicking outside the selection moves it.** Inside, the menu acts on all of it. Outside means you meant that row — so the selection follows the pointer rather than the menu quietly applying to something scrolled off-screen.
**A drag carries the whole selection,** which is why the payload is a list now (`readDraggedIds`), and why `canDropFileNodes` refuses unless *every* file in it can land: a drag that moves four of five and silently skips the fifth is worse than one that will not start.
**A selection belongs to the folder it was made in.** Changing folder or account drops it — rows left selected off-screen make the delete two folders later a surprise.
## Verified
Mock stack, visible Chrome tab, four uploaded files:
| | result |
|---|---|
| plain click | one row; DOM checked, not just the screenshot |
| ctrl-click | two non-adjacent rows, bar says "2 items selected" |
| shift-click | the run a–c, "3 items selected" |
| Move to… on 3 | "Move 3 items", **Move here** disabled at their current parent, all three moved in one action, selection cleared |
| Delete on 3 | "Delete 3 items?", all three gone, one toast |
| click empty space | selection cleared (this was missing; found while testing and fixed) |
| folder change | selection cleared |
**The drag gesture itself is not hand-verified** — HTML5 drag-and-drop does not respond to synthetic mouse events. Its two new pure pieces are unit-tested instead: `readDraggedIds` (one id, many ids, and a drag carrying nothing of ours) and `canDropFileNodes` (all-or-nothing, a folder into itself, the empty list, the top level).
`typecheck` clean, 603 web + 110 server tests, `i18n:check` clean, build clean.
**Merged** 2026-09-01 as coffey-labs/ihasmail@34578ba426cb
<sub>Rebuilt from: git history, session transcript.</sub>
This repo is archived. You cannot comment on issues.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Moving or deleting five files meant doing it five times, each with its own confirm.
Rows now select the way a file manager taught everyone:
Two or more selected raises a bar with Move to… and Delete; the row menu offers the same for the whole selection.
The parts that are decisions, not plumbing
One
FileNode/set, not a loop. Not only for the round trip: a loop would apply half the moves and then throw, leaving a selection split across two folders with nothing saying which half went. One call is one answer, andnotUpdatednames whatever the server refused.Right-clicking outside the selection moves it. Inside, the menu acts on all of it. Outside means you meant that row — so the selection follows the pointer rather than the menu quietly applying to something scrolled off-screen.
A drag carries the whole selection, which is why the payload is a list now (
readDraggedIds), and whycanDropFileNodesrefuses unless every file in it can land: a drag that moves four of five and silently skips the fifth is worse than one that will not start.A selection belongs to the folder it was made in. Changing folder or account drops it — rows left selected off-screen make the delete two folders later a surprise.
Verified
Mock stack, visible Chrome tab, four uploaded files:
The drag gesture itself is not hand-verified — HTML5 drag-and-drop does not respond to synthetic mouse events. Its two new pure pieces are unit-tested instead:
readDraggedIds(one id, many ids, and a drag carrying nothing of ours) andcanDropFileNodes(all-or-nothing, a folder into itself, the empty list, the top level).typecheckclean, 603 web + 110 server tests,i18n:checkclean, build clean.Merged 2026-09-01 as coffey-labs/ihasmail@34578ba426
Rebuilt from: git history, session transcript.