From b1ea97c03e603e99a588d8111ab70194effc9e56 Mon Sep 17 00:00:00 2001 From: rzen Date: Thu, 6 Aug 2026 21:02:47 -0400 Subject: [PATCH] =?UTF-8?q?Locality=20means=20the=20board=20the=20drag=20w?= =?UTF-8?q?as=20picked=20up=20from=20=E2=80=94=20a=20rename=20is=20not=20a?= =?UTF-8?q?=20departure?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Minor ruled into 04 ▸ Cross-board copy: the Finder volume model — a mid-drag rename of the source board carries the live drag with it (the drag holds its source store's identity, never a frozen key, which would let a board opened at the vacated path compare equal — the worse, destructive failure). Until the carry ships the residue is recorded: key follows folder, a mid-drag rename finishes a reorder as a copy — cosmetic, seconds-bounded. Implementation filed. Claude-Session: https://claude.ai/code/session_014PtZdPwqZuqEDLc6wZMtEy --- DESIGN/04-interactions.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DESIGN/04-interactions.md b/DESIGN/04-interactions.md index dd80fd3..4ae25e5 100644 --- a/DESIGN/04-interactions.md +++ b/DESIGN/04-interactions.md @@ -18,7 +18,7 @@ Selection, drag & drop, keyboard, clipboard, search. This is where the old app s - **Multi-drag**: dragging any member of a multi-selection drags the whole selection; N contiguous shadows; drop inserts contiguously in preserved relative order — defined, for any multi-selection, as lane `order` first, then card `order` (a cross-lane selection flattens left-to-right, top-to-bottom). - **Locality picks the default — the Finder volume model** (settled): within a board a drag is a **move** (rearranging); between boards it is a **copy** (transferring — the system copy badge shows over the foreign board). **⌥ always forces copy** and **⌘ always forces move**, Finder's exact modifier grammar; each is a no-op where its behavior is already the default. The badge tracks the effective operation live as the cursor crosses a board boundary. - **Within-board ⌥-drag copies**: originals stay, cursor shows the copy badge, fresh-GUID duplicates land at the drop. Lane drags never copy *within their board* — a within-board lane duplicate is **not available by drag** (⌥ is simply ignored there: the drag stays a clean reorder and the badge never shows copy); the duplicate itself is supported, via the clipboard (Lane paste below) — the usual shape: the keyboard path is the canonical one, drag the enhancement (10-accessibility.md). -- **Cross-board copy** (the default): cards and lanes (including multi-selections) drag between open boards; fresh-GUID duplicates land at the drop, originals stay, `created` is kept (a copy is a fork — 01-storage-format.md). Lanes copy cards and all — transferring workflow structure between boards is safe by default. A lane carries exactly its cards — the trash is board-level (`.trash/` — 03-board-ui.md), so there is nothing lane-nested to strip or carry: copy and ⌘-drag move alike transfer the lane's folder as it is (resettled 2026-07-28; the old tombstone-stripping rule is retired with the tombstone model). +- **Cross-board copy** (the default): cards and lanes (including multi-selections) drag between open boards; fresh-GUID duplicates land at the drop, originals stay, `created` is kept (a copy is a fork — 01-storage-format.md). **Locality means the board the drag was picked up from** (ruled 2026-08-06): a drag knows where it came from — the Finder volume model — so a rename or Finder move of the source board absorbed mid-drag is not a departure: the relocation carries the live drag with it (the drag holds its source *store's* identity, not a frozen copy of its key — a frozen key would let a new board opened at the vacated path compare equal to the renamed-away one, turning a copy into a silent cross-board move, the worse failure), and a within-board reorder stays a reorder across a mid-drag rename. Until the carry ships, the recorded residue — the minted key follows the folder, so a mid-drag rename finishes a reorder as a copy — is cosmetic and bounded by the seconds a drag is in flight. Lanes copy cards and all — transferring workflow structure between boards is safe by default. A lane carries exactly its cards — the trash is board-level (`.trash/` — 03-board-ui.md), so there is nothing lane-nested to strip or carry: copy and ⌘-drag move alike transfer the lane's folder as it is (resettled 2026-07-28; the old tombstone-stripping rule is retired with the tombstone model). - **Cross-board move** (⌘-drag): a real filesystem move, works across volumes — identity travels. A moved folder whose UUID already exists in the destination board arrives as a fresh-UUID copy (01-storage-format.md's import-boundary rule); in a compound move (lane with cards, multi-selection) only the colliding folders are reminted — the rest is a true move (01's per-folder degradation). - **Files from Finder**: dropped on a card → copied into its `attachments/` (any *file* type, multi-file; card highlights while hovered). Dropped on lane empty space → creates a card with the file attached, titled with the filename without its extension (multi-file drop: one card per file). **Folders are refused at hover** (settled — the attachment model is flat top-level files, and the importer refuses directories by design): a drag containing only folders never engages — no highlight, no drop proposal, the standard incompatible-payload read; a mixed drag proposes for its files only, and the drop imports the files while a loss row (02-architecture.md's warning tone) names the skipped folders ("Folders can't be attached — 2 skipped"). The create path thereby only ever fires with at least one importable file — no card is minted for an import that cannot succeed. **Created cards land at the drop position** (settled): resolved through the same card-grid zones an ordinary card drag uses, shadow included — drops are positional everywhere, and append-at-bottom stays the creation *trio's* rule, not the drop's. A multi-file drop shows **one nominal-height shadow per incoming file** (the multi-drag precedent; when macOS withholds item counts during hover the count floors at one shadow, the commit unaffected). **A release on the lane header resolves to the topmost position** (settled — forgiving beats a dead stripe: the header's chrome roles don't collide with a file payload). **The landing shadow is the create path's whole feedback** (settled): no lane-level highlight on top — each target gets one clear signal, and the card-attach highlight exists precisely because that target has no shadow. - **A foreign reload mid-drag re-grounds the drag, never corrupts the drop** (settled — a two-second drag racing agent edits is the designed concurrency). Three rules compose: (1) **geometry re-derives** — the frozen-at-drag-start inputs are the *dragged items'* sizes and the physical pointer only (03-board-ui.md ▸ Motion); the analytic resting zones recompute against each new snapshot, so a foreign lane-count re-divide mid-drag just moves the zones and the next proposal targets the board as it now is. (2) **Proposals re-validate by liveness** — a proposal whose target lane vanished in the reload is invalidated; the shadow withdraws and no proposal stands until the pointer reaches a live target, and **release with no valid proposal cancels** — items return, nothing is written; a card is never filed under a vanished parent. (3) **An emptied drag cancels itself** — drag membership is already a UUID set that vanished items leave silently (02-architecture.md); when the *last* dragged item vanishes the replica dissolves and release is a no-op. Partial vanishing drops the survivors, matching the pending-cut precedent. **A cross-board lane arrival pre-divides the destination strip during hover** (settled): while a foreign lane drag proposes into a board, the destination's standard width is computed with the arriving run's units included, so the shadow draws at the width the lane will actually take — without this it overflows the strip (the pathfinder's stripWidthUnits). The first entry samples the un-widened standard for one frame before hysteresis settles — accepted, imperceptible.