Pivot the trash to a materialized .trash — cards only, no Put Back
The tombstone model is retired wholesale: deletion is a move into the reserved <root>/.trash/, always entering at the top (rank minting — no deleted: key, no timestamp sort), with modified stamped for the future age-based purge. Lanes are never trashed — lane delete is physical, native undo in-session the net. Put Back is gone: restore is drag-out or cut+paste (⌘X now enabled in the trash as the keyboard restore path). Trash cards are ordinary cards — search, selection, clipboard, and rendering uniform — and one container boundary replaces the effective-liveness, ancestor-walk, and kind-homogeneity machinery across 02 and 04. Delete stages by place (board moves, trash deletes permanently under the recoverability confirm); legacy deleted: keys migrate without destroying (cards relocate, lanes return live, notices). Templates exclude .trash; Duplicate carries it; the agent guide teaches delete-as-move. Materialized reserved lanes are named as a pattern — .archive is WISHLIST #8, and the backed-out release-presentation question is marked open in 03. Ruling: Redesign board Resolved card e8f1a6c2; 19 mooted tombstone-era rulings moved to the board's new Mooted lane. Claude-Session: https://claude.ai/code/session_01CqjXB7ASoWtbyoGod68k97
This commit is contained in:
+21
-22
@@ -5,7 +5,7 @@ Selection, drag & drop, keyboard, clipboard, search. This is where the old app s
|
||||
## Selection
|
||||
|
||||
- Cards: click selects; ⌘-click toggles; ⇧-click range-extends; click-drag rubber-bands across lanes. Lanes: ⌘/⇧-click multi-select.
|
||||
- **The range anchor** (settled — standard macOS list semantics): the anchor is the last plain- or ⌘-clicked item, per board window, transient — never persisted. ⇧-click ranges from anchor to target in the flatten order (cards), lane order (lanes), or the trash's sort filtered to kind (its skipping rule below), replacing the selection and leaving the anchor in place. A marquee and wholesale selections (Select All) set no anchor, so a following ⇧-click acts as a plain click; a reload that drops or liveness-flips the anchor clears it.
|
||||
- **The range anchor** (settled — standard macOS list semantics): the anchor is the last plain- or ⌘-clicked item, per board window, transient — never persisted. ⇧-click ranges from anchor to target in the flatten order (cards), lane order (lanes), or the trash's own order (no kinds there — cards only, The trash below), replacing the selection and leaving the anchor in place. A marquee and wholesale selections (Select All) set no anchor, so a following ⇧-click acts as a plain click; a reload that drops or liveness-flips the anchor clears it.
|
||||
- Selection is **homogeneous**: cards XOR lanes.
|
||||
- **Board background** — the margins around and between lanes, and below short content (settled): a plain click clears the selection — the pointer twin of Escape's deselect, Finder's behavior; modified clicks (⇧/⌘) are no-ops there — extension needs an item to extend to; the background is also a rubber-band origin surface on the live side, alongside lane empty space (live) and the trash column's empty space (trashed).
|
||||
- Lane empty-space: single click selects the lane (click again to unselect); double click creates a card at the bottom, title editor focused. **The lane header is click-to-select too** (settled — a full lane has no empty space left): a plain click on the title bar selects the lane — **and toggles like empty space** (settled): a click on the already-selected lane's header unselects, one lane-click behavior everywhere, so a full lane keeps a pointer path out of selection; the drag surface (03-board-ui.md ▸ Lane) engages only on movement — the click-vs-drag split cards already have.
|
||||
@@ -18,17 +18,17 @@ 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 copy **strips tombstoned cards**: the copy transfers content, and trash isn't content (09-templates.md's instantiation precedent — a board isn't born with trash); the tombstoned originals stay recoverable in the source board. A ⌘-drag *move* carries them whole — the folder moves as-is, and they land in the destination's trash.
|
||||
- **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 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 was tombstoned or vanished in the reload is invalidated (tombstoned lanes are never drop targets — The trash below); 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 `deleted:` 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.
|
||||
- **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.
|
||||
|
||||
## Clipboard
|
||||
|
||||
- ⌘X/⌘C/⌘V on cards **and lanes** (resettled — lanes joined the clipboard so cross-board structure transfer has a keyboard path under the every-function contract; the cards-XOR-lanes selection rule means the clipboard holds cards or lanes, never both). Hybrid clipboard: pasteboard carries a JSON manifest + plain text; full folder snapshots staged in Application Support so paste reproduces the item byte-for-byte — cards, attachments and all — across boards. Each manifest entry embeds the full `index.md` as a staging-less fallback (a lane entry embeds its cards' too, attachment-less). **Staging lifecycle** (settled): snapshots are staged **eagerly at ⌘C/⌘X time** — copy captures the source as it is at the gesture, immune to later deletion or unmount — and the store holds at most the *current* copy: a new Lanework copy replaces the previous snapshot, and a sweep at launch and on each copy purges entries the pasteboard no longer references (another app taking the pasteboard orphans the snapshot; the next sweep collects it). The snapshot survives relaunch exactly as long as the pasteboard still points at it — a copy made before quitting pastes whole after restart. **A paste is an import boundary, so normalization applies** (settled 2026-07-28 — 01-storage-format.md's loose-file rule): loose files the staged snapshot carries beside a card's `index.md` land in the pasted card's `attachments/`, Finder-renamed on collision — nothing the snapshot preserved is dropped on arrival. **A degraded paste is loud, never silent** (the banner vocabulary — 02-architecture.md): if the staged snapshot is missing or unreadable at paste time, paste falls back to the embedded `index.md` — content intact, files absent — and a **loss row** (warning tone, user-dismissed) names what was lost with true totals, attachments and loose files alike, folding plurals ("Pasted 'Fix login' without its 3 attachments"; "Pasted 2 items without their 5 files" — BannerCenter owns the phrasing, the manifest records each entry's staged file count so the totals stay honest); the user never discovers an empty `attachments/` later. **The pasteboard is re-read lazily, and the brief lie is accepted** (settled): changeCount is checked on activation, on menu validation, and before paste — no timers; a background app taking the pasteboard while Lanework stays frontmost can leave Edit ▸ Paste enabled until the next check, and the paste itself re-validates and no-ops — nothing stale ever lands, which is the guarantee that matters.
|
||||
- **Cut is Finder-style deferred**: cut items dim in place until paste moves them; voided if another app takes the pasteboard or the source board closes; second paste materializes copies. **Deletion voids per item**: a cut item that is tombstoned or vanishes externally before paste drops out of the pending cut — 02-architecture.md's UUID-set rule; transient state never resurrects what's gone — so paste moves only the survivors, and a cut voided down to nothing is simply void (paste disabled, no error).
|
||||
- Paste lands after the anchor card (or appends to a selected lane); a multi-selection anchors at its last member in flatten order — the ⌘N target rule's shared anchor (The map below). Copies keep `created` (a duplicate is a fork) and take fresh GUID/`order`/`modified`. **A tombstoned selection never anchors paste** (settled — the ⌘N target rule's own wording, returned to the precedent it cites): ⌘V stays enabled and behaves exactly as with nothing selected — a card payload appends to the last-active lane, a lane payload lands at the board's right end; the trash is never the destination (▸ The trash), and a trashed card's live disk-lane never leaks in as "the selected card's lane".
|
||||
- **Lane paste** lands after the anchor lane — the selected lane, or the selected card's lane (several selected: the last, per the shared anchor rule); nothing selected = the board's right end. Semantics mirror the drag pair above exactly: a pasted *copy* takes fresh GUIDs throughout and **strips tombstoned cards**; a cut-paste is the ⌘-drag move — the folder moves whole, tombstoned cards landing in the destination's trash. **Pasting into the source board is supported and is the within-board lane duplicate** (settled): fresh GUIDs and tombstone-stripping apply as anywhere else, no menu-validation special case — the drag path deliberately lacks this operation (⌥ ignored on lane drags, above), the clipboard is its one home.
|
||||
- **Cut is Finder-style deferred**: cut items dim in place until paste moves them; voided if another app takes the pasteboard or the source board closes; second paste materializes copies. **Deletion voids per item**: a cut item that is deleted (moved to the trash or destroyed) or vanishes externally before paste drops out of the pending cut — 02-architecture.md's UUID-set rule; transient state never resurrects what's gone — so paste moves only the survivors, and a cut voided down to nothing is simply void (paste disabled, no error).
|
||||
- Paste lands after the anchor card (or appends to a selected lane); a multi-selection anchors at its last member in flatten order — the ⌘N target rule's shared anchor (The map below). Copies keep `created` (a duplicate is a fork) and take fresh GUID/`order`/`modified`. **A trash selection never anchors paste** (settled — the ⌘N target rule's own wording, returned to the precedent it cites): ⌘V stays enabled and behaves exactly as with nothing selected — a card payload appends to the last-active lane, a lane payload lands at the board's right end; the trash is never the destination (▸ The trash), and a trashed card's live disk-lane never leaks in as "the selected card's lane".
|
||||
- **Lane paste** lands after the anchor lane — the selected lane, or the selected card's lane (several selected: the last, per the shared anchor rule); nothing selected = the board's right end. Semantics mirror the drag pair above exactly: a pasted *copy* takes fresh GUIDs throughout; a cut-paste is the ⌘-drag move — the folder moves whole (nothing lane-nested to strip or carry — the trash is board-level, resettled 2026-07-28). **Pasting into the source board is supported and is the within-board lane duplicate** (settled): fresh GUIDs apply as anywhere else, no menu-validation special case — the drag path deliberately lacks this operation (⌥ ignored on lane drags, above), the clipboard is its one home.
|
||||
|
||||
## Keyboard
|
||||
|
||||
@@ -39,11 +39,11 @@ Selection, drag & drop, keyboard, clipboard, search. This is where the old app s
|
||||
- **Arrows**: spatial card navigation (nearest card in the direction, across interior grid columns and lanes); with a lane selected, ←/→ move lane selection; ⇧-arrow extends — except **⇧↑/⇧↓ in the lane domain, which are inert** (settled: there is nothing above the lane domain and no vertical range within it); selection scrolls into view; all grammar keys inert while a title editor is focused, and menu dispatch narrows to the text domain (focused-editor rule below).
|
||||
- **⌥-arrows jump**: ⌥↑/⌥↓ to the current lane's first/last card; ⌥←/⌥→ to the first/last lane. **The horizontal jumps land on a card** (settled — ⌥↑ is the keyboard's one entry to lane selection, so ⌥←/⌥→ never select the lane itself): the first card of the first/last *non-empty* lane, scanning inward past empty lanes; ⌥→ prefers the shown non-empty trash — its first entry — per the last-container rule (The trash below). **⌥↑ escalates into the lane domain** (settled — the keyboard's one entry to lane selection): with the lane's first card already selected, ⌥↑ selects the *lane* itself — up in the hierarchy sense, the same key one press deeper; with a lane selected, ↓ (or ⌥↓) descends back into its cards at the first (last) card, and ⌥↑ is inert. **An empty selection seeds at the first lane's first card** on any plain arrow (deterministic origin; the ⌥-jumps behave as specified regardless) — two ⌥↑ presses from nothing reach the lane domain.
|
||||
- **Return** on a selected lane: creates a card at its bottom, editor focused; Return commits and re-selects the lane (next Return = next card); ⌘↩ commits and opens the card window. Abandoned placeholders (Escape, empty commit, click-away) are discarded — creating-then-abandoning never leaves an empty card behind (untitled cards exist only when made deliberately, e.g. by an external writer or by clearing an existing title). The placeholder is store-transient overlay state — the named exception to 02-architecture.md's one-way flow; nothing exists on disk until the title commits.
|
||||
- **Inline rename tracks its target by UUID, and vanishing discards it** (the placeholder and card-window kin rules — 02-architecture.md — applied to the third inline editor): a foreign *move* mid-rename is invisible — the editor follows the UUID and the commit writes the title wherever the card now lives; a target that is tombstoned, deleted, or gone at commit time discards the editor and its keystrokes silently (a liveness flip is a vanish; nothing is ever written into a vanished folder, and no partial `index.md` can resurrect deleted data). A write that fails *after* a valid commit is the ordinary one-shot write-failure banner. VoiceOver announces the vanished target per 10-accessibility.md's recovery rule.
|
||||
- **Inline rename tracks its target by UUID, and vanishing discards it** (the placeholder and card-window kin rules — 02-architecture.md — applied to the third inline editor): a foreign *move* mid-rename is invisible — the editor follows the UUID and the commit writes the title wherever the card now lives; a target that is trashed, deleted, or gone at commit time discards the editor and its keystrokes silently (entering the trash is a vanish from the board; nothing is ever written into a vanished folder). A write that fails *after* a valid commit is the ordinary one-shot write-failure banner. VoiceOver announces the vanished target per 10-accessibility.md's recovery rule.
|
||||
- **Return** on a sole selected **card**: inline rename. Return disambiguates on card selection — sole card = rename, lane = create (above) — and is **inert on a multi-card selection**; a lane's rename path is Board ▸ Rename. **Escape** steps outward one layer per press: abandons an open editor; else clears search, returning focus to the board (Search below); else **clears the selection** — the keyboard deselect.
|
||||
- **Focused editor = text domain** (settled): while an inline title editor — rename or the new-card placeholder — is focused, board-scoped menu commands (Delete, New Card, Paste, Move, Style, …) disable via menu validation; text-domain chords route to the field as standard text ops — ⌘Z/⇧⌘Z are the editor's text undo (06-history-undo.md ▸ Undo routing), ⌘X/⌘C/⌘V/⌘A act on the text. The one board-command carve-out is **Open Card ⌘↩**, which stays enabled to commit the edit — placeholder or rename — and open the card window. Exits are otherwise unchanged: Return commits, Escape abandons; click-away splits by editor kind — a **rename commits** (focus loss = commit, matching the card window's title field in 05-card-window.md and the branch-switch parenthetical in 06-history-undo.md), while the **placeholder discards** per its rule above, the deliberate exception because nothing exists on disk yet.
|
||||
- **Caret chords yield to any focused text control** (settled): Board ▸ Move Left/Move Right ⌘←/⌘→ and the width pair ⌥⌘←/⌥⌘→ disable via menu validation whenever *any* text control has keyboard focus — inline title editors, the board search field, board-popover fields (rename, git identity, remote), and card-window fields — because an enabled menu key equivalent fires before the field ever sees the key, and ⌘←/⌘→ are the standard line-start/end caret chords. Caret motion always wins in text (the Safari pattern: ⌘← is Back, yet moves the caret while a field is focused); the lane commands re-enable the moment focus returns to the board. This is a narrow, per-command broadening of the focused-editor rule, not a general one: board commands whose chords carry no text meaning keep their surface-specific dispatch — in particular the search field's board-commands-stay-enabled rule (Search below) — and the search field's explicitly ruled ⌘⌫ steal (File ▸ Delete, not delete-to-line-start) stands.
|
||||
- **⌫** on a live selection: delete (tombstone) — the plain-key synonym for File ▸ Delete ⌘⌫ (see The map). Grammar, not a menu item: giving it a menu home would require a second "Delete"-titled item, which would collide for title-matched remapping (Configurable bindings). Inert while a title editor is focused, like every grammar key.
|
||||
- **⌫** on a selection: delete — the plain-key synonym for File ▸ Delete ⌘⌫, staged by place like the menu item (see The map). Grammar, not a menu item: giving it a menu home would require a second "Delete"-titled item, which would collide for title-matched remapping (Configurable bindings). Inert while a title editor is focused, like every grammar key.
|
||||
- The card window speaks the same grammar: **Return** in Preview enters Edit, **Escape** returns to Preview (05-card-window.md) — plain keys, not menu items.
|
||||
- These plain-key behaviors are platform grammar (Finder's own Return/arrows aren't remappable either) and sit below the remapping mechanism, which handles modifier chords on menu items only — see Configurable bindings.
|
||||
|
||||
@@ -52,23 +52,22 @@ Selection, drag & drop, keyboard, clipboard, search. This is where the old app s
|
||||
Every command is a menu item. The full inventory — every command and action, its default binding, applicable context, and customizability class — lives in **11-command-nexus.md**, the single source of truth for what the app can do; the command titles there are the stable strings the remapping mechanism keys on (Configurable bindings below). The rules below are the behavior behind those bindings and stay normative here.
|
||||
|
||||
- **⌥⌘↑/⌥⌘↓ sort within the lane** (the move-vs-jump question, resettled: *card* moves live on the ⌥⌘ chord, joining ⌥⌘←/⌥⌘→ lane width in a "⌥⌘ modifies" family; plain ⌥-arrows stay jumps; plain ⌘↑/⌘↓ are unassigned): the selected card(s) move one position within the lane — logical `order`, across interior masonry columns (10-accessibility.md's logical-order rule). A non-contiguous multi-selection **gathers on the first press**: the cards collect into a contiguous block anchored at the first selected card (first = lowest logical order; the rest follow in preserved relative order), and subsequent presses move the block one position. **Cards never change lanes by ⌘-arrow** (settled): inter-lane movement is drag or Cut/Paste (the clipboard rules above), so ⌥⌘↑/⌥⌘↓ disable when a card selection spans lanes and ⌘←/⌘→ are inert on card selections. With a **lane** selected, ⌘←/⌘→ move the lane one slot — closing 10-accessibility.md's lane-move defect — and ⌥⌘↑/⌥⌘↓ are inert.
|
||||
- **⌫/⌘⌫ delete** (unchanged): tombstone into the trash quasi-lane (03-board-ui.md); lanes included, no dialog. Selection moves to the deleted item's successor sibling, Finder-style (next card in the lane, next lane on the board; the last sibling's predecessor otherwise; empty container = nothing selected) — repeated ⌫ walks down a lane. Deliberate deletes pick a successor; *external* vanishing never does (02-architecture.md's reload-survival rule: the selection just shrinks). On a **tombstoned** selection ⌘⌫ is **Put Back** instead — Finder's exact symmetry (⌘⌫ trashes and un-trashes). The dual role is carried by **twin menu items sharing the chord** — File ▸ Delete ⌘⌫ and File ▸ Put Back ⌘⌫, validation enabling exactly one by selection state; AppKit routes a shared key equivalent to the enabled item (Finder ships this exact pair as Move to Trash/Put Back; ours says Delete per 03-board-ui.md's naming constraint). Both titles stay stable (titles-are-API), and each is independently remappable — remapping one never moves the other's role. Plain ⌫ performs the same tombstone as fixed grammar (see Grammar above) — there is no Edit ▸ Delete item, so the two Delete-titled homes never collide for title-matched remapping.
|
||||
- **Select All**: all visible cards on the board — filter-respecting, like every surface (Search below). **On the active trash side it selects by kind** (settled): with the trash visible and a non-empty tombstoned selection, Select All selects all visible trash entries of the current selection's kind — the kind-homogeneity invariant decides which "all" is meant; in every other state it selects all visible live cards.
|
||||
- **⌫/⌘⌫ delete** (resettled 2026-07-28): on cards, a move into the trash (`.trash/`, top position — 03-board-ui.md); on a **trash** selection the same chord deletes **permanently** (one Delete vocabulary, staged by place — confirmation per 03's recoverability rule); on lanes, a real delete — folder and contents, no dialog, native undo the net (03 ▸ Trash). Selection moves to the deleted item's successor sibling, Finder-style (next card in the lane, next lane on the board; the last sibling's predecessor otherwise; empty container = nothing selected) — repeated ⌫ walks down a lane. Deliberate deletes pick a successor; *external* vanishing never does (02-architecture.md's reload-survival rule: the selection just shrinks). **Put Back is retired with the tombstone model** (resettled 2026-07-28): File ▸ Delete is the chord's only owner — no twin menu items, no shared-equivalent routing; restore is drag-out or ⌘X/⌘V (The trash below). Plain ⌫ performs the same delete as fixed grammar (see Grammar above) — there is no Edit ▸ Delete item, so the two Delete-titled homes never collide for title-matched remapping.
|
||||
- **Select All**: all visible cards on the board — filter-respecting, like every surface (Search below). **On the active trash side it selects the trash** (resettled 2026-07-28): with the trash visible and a non-empty trash selection, Select All selects all visible trash cards; in every other state, all visible live cards — the container boundary decides which "all" is meant (The trash below).
|
||||
- **The contract's one carve-out is configuration** (settled): form-like git and board setup — add git, add/change remote, branch switching and creation, commit identity, credentials — lives in the board popover only, its committed home; its keyboard path is Board Info (⌘I) plus Tab-reachable controls (10-accessibility.md's Full Keyboard Access). Recurring remote *operations* stay under the contract: Board ▸ Pull and Board ▸ Push are menu items (no default chord, remappable; validation enables them only on remote-backed boards — 07-sync-collab.md).
|
||||
- **⌘N target rule** (settled): with a card selected, the new card is created in that card's lane, immediately after it (paste-anchor consistency); with a lane selected, appended at its bottom (Return consistency); **a multi-selection anchors at its last member in flatten order** (settled — lane `order`, then card `order`, the multi-drag order; the same anchor serves paste): creation follows the last selected card, or appends to the last selected lane; the lane header's new-card button **overrides this rule** — the click names its target lane, selection notwithstanding (11-command-nexus.md ▸ Pointer grammar); with nothing selected — or a **tombstoned** selection, which never anchors creation — the **last-active lane** — the lane that most recently held selection or a creation in this window session — falling back to the first lane. Title editor focused; same placeholder/abandon semantics as Return-creation. **Zero-lane board** (hand-made, or every lane deleted): card creation and card paste have no target — New Card, Return-creation, and Paste with a *card* payload disable via menu validation until a lane exists. New Lane (⇧⌘N) is one way in; Paste with a **lane** payload is the other — it stays enabled and lands at the board's right end (the lane-paste rule above), so cross-board structure transfer never needs a lane to exist first.
|
||||
- **⌘N target rule** (settled): with a card selected, the new card is created in that card's lane, immediately after it (paste-anchor consistency); with a lane selected, appended at its bottom (Return consistency); **a multi-selection anchors at its last member in flatten order** (settled — lane `order`, then card `order`, the multi-drag order; the same anchor serves paste): creation follows the last selected card, or appends to the last selected lane; the lane header's new-card button **overrides this rule** — the click names its target lane, selection notwithstanding (11-command-nexus.md ▸ Pointer grammar); with nothing selected — or a **trash** selection, which never anchors creation — the **last-active lane** — the lane that most recently held selection or a creation in this window session — falling back to the first lane. Title editor focused; same placeholder/abandon semantics as Return-creation. **Zero-lane board** (hand-made, or every lane deleted): card creation and card paste have no target — New Card, Return-creation, and Paste with a *card* payload disable via menu validation until a lane exists. New Lane (⇧⌘N) is one way in; Paste with a **lane** payload is the other — it stays enabled and lands at the board's right end (the lane-paste rule above), so cross-board structure transfer never needs a lane to exist first.
|
||||
|
||||
### The trash, keyboard-first (settled)
|
||||
### The trash, keyboard-first (resettled 2026-07-28 — the materialized trash)
|
||||
|
||||
The trash quasi-lane (03-board-ui.md ▸ Trash) speaks the same keyboard language when shown; hidden, it is invisible to every gesture — and **hiding it clears a tombstoned selection** (settled): nothing invisible stays selected, so the Show Trash toggle-off drops the selection (and with it Put Back / Delete Immediately validation) rather than leave commands enabled against rows nobody can see. Rules:
|
||||
The trash lane (03-board-ui.md ▸ Trash — cards moved into `<root>/.trash/`, cards only, no lane entries) speaks the board's ordinary keyboard language when shown; hidden, it is invisible to every gesture — and **hiding it clears a selection of trash cards** (nothing invisible stays selected, so the toggle-off drops the selection rather than leave commands enabled against rows nobody can see). Because trash cards are ordinary cards, the old kind- and liveness-boundary machinery is retired: no lane entries, no ancestor walks, no entry-vs-universe split. Rules:
|
||||
|
||||
- **Navigation**: the shown trash is the **last container for card navigation** — arrows walk into and out of it, and ⌥→ jumps to it. The quasi-lane itself is never selectable *as a lane* (no lane op applies to it): with a lane selected, ←/→ and ⌥→ stop at the last real lane.
|
||||
- **Dropping a live card on the shown trash deletes it** (settled 2026-07-28): the drag becomes the pointer's delete gesture — release tombstones the dragged card(s), exactly the ⌫ tombstone. The drop diverges from positional drops in one way: **the shadow always takes the topmost position** — which the sort makes honest, not arbitrary: the trash orders by `deleted` newest-first, so a fresh tombstone genuinely lands on top. Lanes are not deliverable this way (a lane drag proposes only lane slots); the trash stays undropppable-into while hidden, like every gesture. Shipped 2026-07-28 (33bf425): cross-board arrivals and ⌥-copies refuse too (a transfer-and-delete compound and a copy-into-the-trash are operations the design doesn't name), and a refusal falls through to the strip retarget rather than cancelling the held drag.
|
||||
- **Moves are inert across the boundary**: no move or paste ever targets the trash (deleting is ⌫/⌘⌫), and ⌥⌘↑/⌥⌘↓ are inert *on* tombstoned cards (moving out is Put Back or drag-to-restore).
|
||||
- **Selection is homogeneous by liveness** (extending the homogeneous-selection rule): a selection never mixes live and tombstoned cards. Select All selects visible live cards only; a rubber-band stays on the side of the boundary it started on. **⇧-arrow extension stops at both boundaries — liveness and kind** (settled, the rubber-band's rule applied to the keyboard): a ⇧-arrow whose next step would cross from live cards into the trash (or back), or from card entries onto a lane entry within it, is simply inert — the held range is never silently dropped for a move-and-replace, and the invariants below are unbreakable by keyboard. Plain arrows still walk across (navigation moves, extension stops). Menu validation stays binary — Delete for live selections, Put Back / Delete Immediately for tombstoned ones. External liveness flips can't breach the invariant: a reload that flips `deleted:` on a selected card ejects it from the selection (02-architecture.md's reload-survival rule — a flip is a vanish from its side of the boundary), so validation never sees a mixed selection.
|
||||
- **Tombstoned lane entries are full keyboard citizens, homogeneous by kind**: arrows walk every trash entry in its sorted order — card and lane entries alike (a lane's single restorable entry, 03-board-ui.md ▸ Trash) — and the board's cards-XOR-lanes rule extends into the trash: a selection never mixes card entries and lane entries (on top of never mixing live and tombstoned). Put Back (⌘⌫) and Delete Immediately (⌥⌘⌫) apply to lane entries exactly as to cards — a put-back lane returns whole, cards and all. A lane entry is not draggable (its entry is a compact row, not the lane); its copy-out is ⌘C only, and its move-out is Put Back. **Pointer ranges keep the kind invariant by skipping, not stopping** (settled): a ⇧-click range collects only entries of the anchor's kind, skipping other-kind rows inside the span — the pointer names an unambiguous same-kind target, so it skips where the keyboard (which must cross row by row) goes inert; a trash-side marquee takes the kind of the topmost intersected entry and selects only that kind. Both keep every selection kind-homogeneous, each in the idiom of its input. **The column's gesture surface is full height** (settled): the trash column carries a full-height backdrop like the board background, so a marquee can arm from the blank area below the last row — the board side has no dead zone and the trash side doesn't either.
|
||||
- **Clipboard: copy out only.** ⌘C (cards and lane entries), ⌥-drag, and the cross-board drag default (cards) always yield *live* copies — `deleted:` is stripped on paste/duplicate/drop, like copying a file out of Finder's Trash; a lane entry's copy additionally strips its tombstoned interior cards (the lane-copy rule — copies transfer content, and trash isn't content). ⌘X is disabled: the move-out vocabulary is Put Back or drag-to-restore, nothing else. The *cross-board restore-move* (⌘-drag below) needs no command of its own — its keyboard equivalent is the composition Put Back → ⌘X → ⌘V in the destination: same folder, same identity.
|
||||
- **Everything edit-shaped is disabled** on tombstoned selections — Open Card, Rename, Style… (File ▸ Duplicate is untouched: it duplicates the board, never the selection — 11-command-nexus.md; card copies out of the trash are ⌘C or ⌥-drag, which name a live destination). Finder file drops (attachment import) on tombstoned cards are inert — 03-board-ui.md's no-editing-in-the-trash.
|
||||
- **Drag-to-restore follows the locality model**: dropping a tombstoned card into one of its own board's lanes restores it at the drop position (`deleted:` removed, `order` set). Dropped on *another* board it follows the copy default — a live copy lands there and the tombstoned original stays in the source trash (copy-out, like ⌘C); ⌘-drag forces the true cross-board restore-move (the tombstone leaves the source board; ordinary cross-board move semantics, `deleted:` cleared at the destination).
|
||||
- **Navigation**: the shown trash is the **last container for card navigation** — arrows walk into and out of it, and ⌥→ jumps to it. The trash lane itself is never selectable *as a lane* (no lane op applies to it): with a lane selected, ←/→ and ⌥→ stop at the last real lane.
|
||||
- **Dropping a live card on the shown trash deletes it** (settled): the drag is the pointer's delete gesture — release moves the dragged card(s) into `.trash/`. The drop diverges from positional drops in one way: **the shadow always takes the topmost position** — honest, not arbitrary: every trash arrival mints a rank above the current top (03 ▸ Trash), so a fresh delete genuinely lands on top. Lanes are not deliverable this way (a lane drag proposes only lane slots); the trash takes no drops while hidden, like every gesture. Cross-board arrivals and ⌥-copies refuse too (a transfer-and-delete compound and a copy-into-the-trash are operations the design doesn't name), and a refusal falls through to the strip retarget rather than cancelling the held drag.
|
||||
- **Selection keeps one container boundary**: a selection never mixes trash cards with board cards — a single container rule replacing the old liveness law, because Delete would otherwise mean two different things in one gesture (move-to-trash vs permanent). The rubber band stays on the side it started on, ⇧-arrow extension stops at the boundary (plain arrows walk across — navigation moves, extension stops), and Select All with a non-empty trash selection selects all visible trash cards, otherwise all visible live cards. Menu validation stays binary by container: Delete = move to trash on board selections, Delete = permanent on trash selections (03 ▸ Trash). An external move observed by reload re-resolves the selection by presence, as everywhere (02-architecture.md).
|
||||
- **Within-trash moves are inert**: no move or paste ever targets the trash (deleting is ⌫/⌘⌫ or the drag above), and ⌥⌘↑/⌥⌘↓ are inert on trash cards — the trash's order is its arrival order, not a workspace to arrange.
|
||||
- **Clipboard: the restore path.** ⌘C copies a trash card (a live copy lands wherever pasted — like copying out of Finder's Trash); **⌘X works** (resettled — it was disabled under the tombstone model): cut in the trash, paste into a lane is the keyboard-native restore, an ordinary folder move (10-accessibility.md's drag-free contract). **A card entering the trash voids its pending cut** (the deliberate-removal rule): a cut card that gets deleted drops out of the pending cut, as under the old model.
|
||||
- **Everything edit-shaped is disabled** on trash selections — Open Card, Rename, Style…; Finder file drops on trash cards are inert (03's no-editing-in-the-trash). Creation never anchors to the trash: ⌘N and paste with a trash selection fall back to their nothing-selected targets.
|
||||
- **Drag-to-restore follows the locality model**: dropping a trash card into one of its own board's lanes is an ordinary move to the drop position. Dropped on *another* board it follows the copy default — a live copy lands there, the original stays in the source trash; ⌘-drag forces the true cross-board restore-move.
|
||||
|
||||
### Configurable bindings (settled)
|
||||
|
||||
@@ -77,7 +76,7 @@ Custom shortcuts are **system-native, with no in-app remapping UI**: macOS's App
|
||||
- **Menu item titles are API.** The mechanism matches on exact titles — renaming a menu item orphans users' bindings. Titles change only with the deliberateness of a schema change.
|
||||
- **Toggles keep one stable title** with a checkmark state — "Show Trash" stays "Show Trash" when checked, never becomes "Hide Trash". (Same for Edit Body and Raw Source.)
|
||||
- **Undo/Redo are effectively not remappable** — NSUndoManager rewrites their titles dynamically ("Undo Move Card…"), which defeats title matching. Accepted; nobody remaps ⌘Z.
|
||||
- **Two items may share a default chord when validation is mutually exclusive** (Delete / Put Back on ⌘⌫) — AppKit fires the enabled one. Each keeps its own stable title, so remapping stays per-item. Corollary: no two menu items share a *title* either (titles are the remap key), which is why plain-⌫ delete is grammar rather than a second Delete item.
|
||||
- **Two items may share a default chord when validation is mutually exclusive** (a pattern currently unused — Put Back's retirement removed its one instance) — AppKit fires the enabled one. Each keeps its own stable title, so remapping stays per-item. Corollary: no two menu items share a *title* either (titles are the remap key), which is why plain-⌫ delete is grammar rather than a second Delete item.
|
||||
|
||||
## Accessibility
|
||||
|
||||
|
||||
Reference in New Issue
Block a user