Lanes delete into the trash — the design pass

User-proposed and ruled 2026-07-29, retiring the design's sole
destructive delete: lane delete becomes a move into .trash/, exactly as
cards move. Full rationale on the Redesign board's Resolved card;
implementation is post-m12 ("Lanes delete into the trash" on the
Backlog) — the docs deliberately lead the code until then.

- 01: kind: lane always present on lane frontmatter (created with the
  lane, healed on touch, never stripped) — discriminates lane from card
  in the flat .trash/, where an empty lane is shape-identical to a
  card; legacy deleted: on lanes is now simply ignored (no migration,
  no notice — backward compatibility deliberately not funded).
- 03: trashed lane = opaque dimmed row with card count, restores whole
  or purges whole; confirms count lane freight; fixed 03's own copy of
  the "native undo restores a permanent delete" contradiction.
- 04: delete staged by place for lanes too (board→trash no dialog,
  trash→permanent confirmed); lane drop on shown trash deletes;
  kind-homogeneous trash selections, ranges skip by kind; ⌘X/⌘V restore
  rides the existing lane-paste rule.
- 10: trashed lane is one flattened element, never a container.
- 13: lane delete inverse = ordinary move back; recreate-from-capture
  retires — no destructive delete remains outside the trash.
- 08: agent guide v6 — lanes trash by move, kind: lane taught.

Claude-Session: https://claude.ai/code/session_01CqjXB7ASoWtbyoGod68k97
This commit is contained in:
2026-07-29 12:42:49 -04:00
parent 89d4d983e6
commit 1c263b9f2e
6 changed files with 26 additions and 24 deletions
+5 -4
View File
@@ -73,6 +73,7 @@ Body: board description (free Markdown).
|---|---|---|---| |---|---|---|---|
| `order` | number | yes | Rank among lanes, ascending = left-to-right | | `order` | number | yes | Rank among lanes, ascending = left-to-right |
| `width` | integer | no | Width multiplier ≥ 1 (default 1); no cap | | `width` | integer | no | Width multiplier ≥ 1 (default 1); no cap |
| `kind` | string | no | Always `lane` (ruled 2026-07-29) — written at lane creation, healed on touch when absent, never stripped. Redundant with depth on the live board; load-bearing inside `.trash/`, where it discriminates a trashed lane from a card (Deletion below). Cards never carry it. |
Body: lane description / WIP policy / notes. Body: lane description / WIP policy / notes.
@@ -113,12 +114,12 @@ Carried over unchanged — gapped fractional ranks:
The tombstone model is **retired** — no `deleted:` key, no in-place flags, no ancestor-walked hiding. Deletion is a **move**: The tombstone model is **retired** — no `deleted:` key, no in-place flags, no ancestor-walked hiding. Deletion is a **move**:
- **Deleting a card moves its folder into `<board-root>/.trash/`** — a reserved, app-claimed name at board root (joining `CLAUDE.md` and the seeded `.gitignore` on the claimed list; agents and hand-editors should treat it as the app's). `.trash/` holds card folders directly — same shape as a lane's children, no `index.md` of its own. Every arrival lands at the **top**: the move mints an `order` rank above the current topmost, so the trash sorts newest-first by ordinary ranks — no timestamp sort, no tie-break rules. The move **stamps `modified`** (deletion is an edit to the card's story — the deliberate exception to moves-don't-stamp), which is what a future age-based auto-purge will read (deliberate follow-up, not in the initial release). - **Deleting a card or a lane moves its folder into `<board-root>/.trash/`** (lanes joined 2026-07-29 — retiring the design's sole destructive delete) — a reserved, app-claimed name at board root (joining `CLAUDE.md` and the seeded `.gitignore` on the claimed list; agents and hand-editors should treat it as the app's). `.trash/` holds card and lane folders interleaved directly, no `index.md` of its own. Every arrival lands at the **top** regardless of kind: the move mints an `order` rank above the current topmost, so the trash sorts newest-first by ordinary ranks — no timestamp sort, no tie-break rules. The move **stamps `modified`** (deletion is an edit to the item's story — the deliberate exception to moves-don't-stamp), which is what a future age-based auto-purge will read (deliberate follow-up, not in the initial release). A trashed lane keeps its subtree untouched — its cards ride along and come back with it.
- **Deleting a lane is physical** — the folder and its contents are removed. There is no lane trash; the recovery net is native undo in-session (13-native-undo.md) and git history on git boards (06-history-undo.md's delete-never-forgets). - **`kind:` discriminates inside `.trash/`** (ruled 2026-07-29): depth defines meaning *on the live board*, but the trash is flat, and an empty lane folder is shape-identical to a card folder (`<uuid>/index.md` both). Lane `index.md` frontmatter therefore carries **`kind: lane` at all times** — written at lane creation, healed on touch when absent (minimum: the trash move stamps it; the fuller healing posture is the validation-service design, tracked on the Redesign board), never stripped (restore stays a pure move; the key is inert on the live board where depth wins). Cards never carry the key — absent means card. The trash reader's rule: `kind: lane` → lane; no key but UUID-shaped children with their own `index.md` → lane (self-heal the key); neither → card.
- **Restore is a move out**: any move of a card folder from `.trash/` into a lane — drag, or cut+paste — is an ordinary move; there is no restore-specific machinery and no Put Back. Position and lane are chosen at restore time. - **Restore is a move out**: any move of a card folder from `.trash/` into a lane, or of a lane folder to board level — drag, or cut+paste — is an ordinary move; there is no restore-specific machinery and no Put Back. Position (and, for cards, lane) are chosen at restore time.
- A folder that disappears entirely (Finder deletion) is also a delete; the app reflects it — such items are gone, they never enter the trash. - A folder that disappears entirely (Finder deletion) is also a delete; the app reflects it — such items are gone, they never enter the trash.
- Physical removal in-app is the trash's **Delete / Delete Immediately / Empty Trash** (03-board-ui.md). - Physical removal in-app is the trash's **Delete / Delete Immediately / Empty Trash** (03-board-ui.md).
- **Legacy `deleted:` keys migrate on load-and-write, never destroy** (settled): a card carrying `deleted:` is relocated into `.trash/` (key removed — the loose-file relocation posture: detection read-only in the loader, the fix through the Writer, a graceful warning-tone notice, deferred under any read-only lock); a lane carrying `deleted:` returns **live** with the key removed and a notice — resurrection is the safe direction, nothing is destroyed by migration. A `deleted:` key at board level remains meaningless — ignored and logged, preserved verbatim. - **Legacy `deleted:` keys: cards migrate, lanes ignore** (lane clause re-ruled 2026-07-29): a card carrying `deleted:` is relocated into `.trash/` (key removed — the loose-file relocation posture: detection read-only in the loader, the fix through the Writer, a graceful warning-tone notice, deferred under any read-only lock); a lane carrying `deleted:` simply **loads live with the key ignored** — no migration machinery, no key-strip write, no notice (backward compatibility deliberately not funded: old tombstoned lanes reappearing is accepted; the key is inert, preserved verbatim like any unhandled key, logged). The app never writes `deleted:`. A `deleted:` key at board level remains meaningless — ignored and logged, preserved verbatim.
## Malformed input — fail fast ## Malformed input — fail fast
+6 -6
View File
@@ -61,13 +61,13 @@ The window-title widget opens the **board popover** — the one board-level surf
**Resettled 2026-07-28 — the materialized trash.** The tombstone model (a `deleted:` flag on items left in place, rendered by a pure-view quasi-lane) is **retired**: it generated a standing tax of nesting rules — ancestor walks, effective liveness, entry-vs-universe splits, kind-homogeneous selection — that this design replaces wholesale. Deletion is now a **move**: deleting a card moves its folder into **`<board-root>/.trash/`**, a reserved, materialized container (01-storage-format.md). A trashed card is an ordinary card in a special place — search, selection, rendering, styling, and clipboard all treat it exactly like any other card, and `.trash/` is self-describing in Finder and to agents. **Resettled 2026-07-28 — the materialized trash.** The tombstone model (a `deleted:` flag on items left in place, rendered by a pure-view quasi-lane) is **retired**: it generated a standing tax of nesting rules — ancestor walks, effective liveness, entry-vs-universe splits, kind-homogeneous selection — that this design replaces wholesale. Deletion is now a **move**: deleting a card moves its folder into **`<board-root>/.trash/`**, a reserved, materialized container (01-storage-format.md). A trashed card is an ordinary card in a special place — search, selection, rendering, styling, and clipboard all treat it exactly like any other card, and `.trash/` is self-describing in Finder and to agents.
- **Cards only. Lanes are never trashed** (settled): deleting a lane deletes it — folder and contents, physically. The net is undo, not the trash: native undo restores it in-session (13-native-undo.md), git boards keep it reachable forever (06-history-undo.md's delete-never-forgets); undo not surviving relaunch is accepted. No dialog — same posture as before. - **Lanes trash too** (re-ruled 2026-07-29, retiring "cards only" and with it the design's sole destructive delete): deleting a lane moves its folder — subtree intact — into `.trash/`, exactly as a card moves; `kind: lane` in its frontmatter is what tells a trashed lane from a card in the flat container (01-storage-format.md ▸ Deletion), stamped on the way in when absent. The no-dialog posture survives for a better reason: the move is recoverable, so nothing needs confirming. Native undo's inverse is the ordinary move back (13-native-undo.md — the capture/recreate machinery retires). **A trashed lane is an opaque unit**: one distinct dimmed row showing its title and held-card count ("Doing — 5 cards"), no styling accents, never expandable; its cards are invisible to search and not individually addressable — it restores whole or purges whole. The row matches the search filter by lane title only. Lane rows and cards interleave in the one trash column purely by trash rank.
- **Entry is always at the top** (settled): every arrival — ⌫/⌘⌫ delete and drag-to-trash alike — lands at the trash's topmost position, minting an `order` rank above the current top. Newest-first ordering falls out of ordinary ranks by construction: **there is no `deleted:` key and no timestamp sort** — the trash sorts by `order` like any lane. The move **stamps `modified`** (the one exception to moves-don't-stamp — deletion is an edit to the card's story), which is what a future age-based auto-purge will read (deferred, 01-storage-format.md). - **Entry is always at the top** (settled): every arrival, card or lane — ⌫/⌘⌫ delete and drag-to-trash alike — lands at the trash's topmost position, minting an `order` rank above the current top. Newest-first ordering falls out of ordinary ranks by construction: **there is no `deleted:` key and no timestamp sort** — the trash sorts by `order` like any lane. The move **stamps `modified`** (the one exception to moves-don't-stamp — deletion is an edit to the card's story), which is what a future age-based auto-purge will read (deferred, 01-storage-format.md).
- **Rendering**: trailing (rightmost) position when shown, visually distinct — dimmed/hatched header, trash SF Symbol, count badge; no new-card button; not draggable, not resizable, excluded from lane reordering. Fixed one width unit, consumed only while shown; Show/Hide Trash is a re-divide trigger (Layout above). **Visibility**: hidden by default; View ▸ Show Trash toggles (no default chord — ⇧⌘T belongs to the system's Show Tab Bar, 11-command-nexus.md); per-open transient state, resets to hidden, never persisted. Hidden, the trash is invisible to every gesture and to search; shown, its cards participate in the filter **exactly like any other card** — the point of the pivot. - **Rendering**: trailing (rightmost) position when shown, visually distinct — dimmed/hatched header, trash SF Symbol, count badge; no new-card button; not draggable, not resizable, excluded from lane reordering. Fixed one width unit, consumed only while shown; Show/Hide Trash is a re-divide trigger (Layout above). **Visibility**: hidden by default; View ▸ Show Trash toggles (no default chord — ⇧⌘T belongs to the system's Show Tab Bar, 11-command-nexus.md); per-open transient state, resets to hidden, never persisted. Hidden, the trash is invisible to every gesture and to search; shown, its cards participate in the filter **exactly like any other card** — the point of the pivot.
- **No Put Back** (settled): the valuable item is the card; where it goes on the way out is the user's cheap decision. Restoring is an ordinary move out: **drag** a trash card into any lane at any position, or **⌘X in the trash, ⌘V into a lane** — the clipboard works on trash cards like on any card, which is also the keyboard-native restore path (10-accessibility.md). Dropped on another board it follows the drag locality model (04-interactions.md). - **No Put Back** (settled): the valuable item is the card (or lane); where it goes on the way out is the user's cheap decision. Restoring is an ordinary move out: **drag** a trash card into any lane at any position — or a trashed lane row to a lane-strip slot — or **⌘X in the trash, ⌘V** — into a lane for cards, after the anchor lane for a trashed lane (04-interactions.md's lane-paste rule verbatim); the clipboard works on trash items like on any item, which is also the keyboard-native restore path (10-accessibility.md). Dropped on another board it follows the drag locality model (04-interactions.md).
- **No editing in the trash**: trash cards don't open — double-click stops at selection; move it out first. Moving a card to the trash dismisses its open card window (05-card-window.md), and an external move-in observed by reload does the same. - **No editing in the trash**: trash cards don't open — double-click stops at selection; move it out first — and a trashed lane row never expands. Moving a card to the trash dismisses its open card window (05-card-window.md), and an external move-in observed by reload does the same; a lane entering the trash dismisses the open card windows of every card it carries (they entered the trash with it).
- **Permanent deletion**: on a trash card, **Delete (⌫/⌘⌫) is permanent** — one delete vocabulary, staged by place: on the board it moves to the trash, in the trash it removes the folder. **⌥⌘⌫ Delete Immediately** skips the trash from anywhere. Both **confirm exactly where the loss is real** (carried over): on boards without app-managed git history the alert stands between one keystroke and unrecoverable deletion; on git boards they act immediately (delete-never-forgets). **Empty Trash…** (⇧⌘⌫) confirms everywhere and purges the whole `.trash/`, search-independent, the confirmation naming the card count ("Permanently delete 41 cards" — 06-history-undo.md's plural folding); menu validation's "non-empty" reads `.trash/`, not the filtered view. - **Permanent deletion**: on a trash selection, **Delete (⌫/⌘⌫) is permanent** — one delete vocabulary, staged by place: on the board it moves to the trash, in the trash it removes the folder. **⌥⌘⌫ Delete Immediately** skips the trash from anywhere. Both **confirm exactly where the loss is real** (carried over): on boards without app-managed git history the alert stands between one keystroke and unrecoverable deletion; on git boards they act immediately (delete-never-forgets). Confirms name the freight honestly — a trashed lane's alert counts its cards ("Permanently delete lane 'Doing' and its 5 cards"). **Empty Trash…** (⇧⌘⌫) confirms everywhere and purges the whole `.trash/`, lane subtrees walked, search-independent, the confirmation naming the full count ("Permanently delete 41 cards", "… 41 cards and 2 lanes containing 9 more cards" — 06-history-undo.md's plural folding); menu validation's "non-empty" reads `.trash/`, not the filtered view.
- Every trash operation is an ordinary file operation — auto-committed and undoable on git boards; native undo restores a permanent delete in-session on any board (a trash move undoes as a move back — durable across relaunch, since it is just a move). - Every trash operation is an ordinary file operation — auto-committed and undoable on git boards; native undo restores a delete-to-trash in-session on any board (a trash move undoes as a move back — durable across relaunch, since it is just a move), while a permanent delete registers no step (13-native-undo.md: the confirm is the safety).
- **Naming constraint** (carried over): attachment Remove moves files to the *system* Trash (05-card-window.md); board deletion says "Delete" and this container is "Trash" / "Show Trash" — Finder's "Move to Trash" phrasing stays reserved for the system Trash. - **Naming constraint** (carried over): attachment Remove moves files to the *system* Trash (05-card-window.md); board deletion says "Delete" and this container is "Trash" / "Show Trash" — Finder's "Move to Trash" phrasing stays reserved for the system Trash.
- **Materialized reserved lanes are a pattern, not a one-off**: `.trash` is the first; an **archive lane** (`<root>/.archive`, intake criteria to be designed) is the anticipated second (WISHLIST) — same mechanics, ordinary cards in a reserved dot-named container, different entry semantics. Nothing beyond `.trash` is committed yet. - **Materialized reserved lanes are a pattern, not a one-off**: `.trash` is the first; an **archive lane** (`<root>/.archive`, intake criteria to be designed) is the anticipated second (WISHLIST) — same mechanics, ordinary cards in a reserved dot-named container, different entry semantics. Nothing beyond `.trash` is committed yet.
+10 -10
View File
@@ -5,7 +5,7 @@ Selection, drag & drop, keyboard, clipboard, search. This is where the old app s
## Selection ## Selection
- Cards: click selects; ⌘-click toggles; ⇧-click range-extends; click-drag rubber-bands across lanes. Lanes: ⌘/⇧-click multi-select. - 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 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. - **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 — where rows of the other kind are skipped, since lanes rejoined the trash (2026-07-29; 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. - 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). - **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. - 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.
@@ -52,22 +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. 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. - **⌥⌘↑/⌥⌘↓ 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** (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. - **⌫/⌘⌫ delete** (resettled 2026-07-28; lanes rejoined 2026-07-29): on cards *and lanes*, a move into the trash (`.trash/`, top position — 03-board-ui.md; a lane travels subtree-intact, `kind: lane` stamped when absent, no dialog — recoverable now, so nothing needs confirming); on a **trash** selection the same chord deletes **permanently** (one Delete vocabulary, staged by place — confirmation per 03's recoverability rule, a lane's alert counting its cards). 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). - **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). - **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 **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. - **⌘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 (resettled 2026-07-28 — the materialized trash) ### The trash, keyboard-first (resettled 2026-07-28 — the materialized trash)
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: The trash lane (03-board-ui.md ▸ Trash — cards and lanes moved into `<root>/.trash/`; lanes rejoined 2026-07-29 as opaque-unit rows) speaks the board's ordinary keyboard language when shown; hidden, it is invisible to every gesture — and **hiding it clears a trash selection** (nothing invisible stays selected, so the toggle-off drops the selection rather than leave commands enabled against rows nobody can see). Trash cards are ordinary cards; a trashed lane is one opaque row (title + card count) — the old liveness machinery stays retired: no ancestor walks, no entry-vs-universe split, one container boundary plus the board's own kind rule. Rules:
- **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. - **Navigation**: the shown trash is the **last container for card navigation** — arrows walk into and out of it, and ⌥→ jumps to it; inside, plain arrows walk every row, card and lane row alike (navigation crosses kinds). 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. - **Dropping a live card — or lane — on the shown trash deletes it** (lanes extended 2026-07-29): the drag is the pointer's delete gesture — release moves the dragged item(s) into `.trash/`; a lane drag over the shown trash proposes the delete alongside its strip slots. 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. 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). - **Selection keeps one container boundary — and the board's kind rule**: a selection never mixes trash items with board items, and (as everywhere) never mixes cards with lanes — a trash selection is either cards or lane rows, kind-homogeneous like the live board's own grammar. The rubber band stays on the side it started on and selects cards only (as the board marquee does); lane rows join by click grammar, and ⇧-click ranges skip rows of the other kind (resurrecting the 2026-07-28 skip-by-kind ruling, mooted when lanes left the trash and back with them). ⇧-arrow extension stops at the container boundary *and* at a kind boundary (plain arrows walk across — navigation moves, extension stops). Select All with a non-empty trash selection selects all visible trash **cards**, otherwise all visible live cards — Select All is card-scoped everywhere, never lane rows. 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. - **Within-trash moves are inert**: no move or paste ever targets the trash (deleting is ⌫/⌘⌫ or the drag above), and ⌥⌘↑/⌥⌘↓ are inert on trash rows — 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. - **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 is the keyboard-native restore, an ordinary folder move (10-accessibility.md's drag-free contract) — a card pastes into a lane, a trashed lane pastes after the anchor lane (the lane-paste rule above, verbatim; the payload kinds never mix because the selection never does). **An item entering the trash voids its pending cut** (the deliberate-removal rule): a cut card — or lane — 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. - **Everything edit-shaped is disabled** on trash selections — Open Card, Rename, Style…, and lane width ops on lane rows; Finder file drops on trash rows 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. - **Drag-to-restore follows the locality model**: dropping a trash card into one of its own board's lanes — or a trashed lane row onto its own board's strip — 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) ### Configurable bindings (settled)
+2 -1
View File
@@ -17,7 +17,8 @@ The app silently maintains a `CLAUDE.md` in every board — a condensed, agent-f
- Creating cards (mkdir UUID, write `index.md`, ordering rules) — plain UTF-8, no BOM, preserving each file's existing line endings (01-storage-format.md's encoding contract). - Creating cards (mkdir UUID, write `index.md`, ordering rules) — plain UTF-8, no BOM, preserving each file's existing line endings (01-storage-format.md's encoding contract).
- Moving between lanes (folder move), reordering (gapped ranks, only touch the moved item). - Moving between lanes (folder move), reordering (gapped ranks, only touch the moved item).
- Deletes — move the card folder into `<root>/.trash/` (top position; never delete a card folder outright unless permanence is meant); colors/icons. - Deletes — move the card **or lane** folder into `<root>/.trash/` (top position; never delete a folder outright unless permanence is meant; lanes rejoined 2026-07-29 → guide v6); colors/icons.
- v6 additionally teaches **`kind: lane`**: lane frontmatter always carries it (include it when creating a lane; stamp it when trashing a lane that lacks it) — it is what tells a trashed lane from a card inside the flat `.trash/` (01-storage-format.md ▸ Deletion).
- New in the rewrite: **attachments** (the `attachments/` convention, importing files) — including the rule that **card files belong in `attachments/`**: a loose file written beside `index.md` will be relocated there by the app with a notice (01-storage-format.md's loose-file carve-out), so agents should put it there in the first place. - New in the rewrite: **attachments** (the `attachments/` convention, importing files) — including the rule that **card files belong in `attachments/`**: a loose file written beside `index.md` will be relocated there by the app with a notice (01-storage-format.md's loose-file carve-out), so agents should put it there in the first place.
- New in the rewrite: **`modified-by` self-stamping** — stamp files you write; re-stamp every write *and every move* (the app clears it on its own writes; a bare folder move leaves the card unstamped); self-commit instead when you need exact authorship. - New in the rewrite: **`modified-by` self-stamping** — stamp files you write; re-stamp every write *and every move* (the app clears it on its own writes; a bare folder move leaves the card unstamped); self-commit instead when you need exact authorship.
- New in the rewrite: a pointer to the optional **`CLAUDE.user.md`** (see below), instructing agents to read it when present. - New in the rewrite: a pointer to the optional **`CLAUDE.user.md`** (see below), instructing agents to read it when present.
+1 -1
View File
@@ -15,7 +15,7 @@ The stance is committed in 00-vision.md: **accessibility is a requirement of "na
- **VO cursor and app selection are independent** (Finder-style): moving the VoiceOver cursor never mutates selection. VO-Space on any selectable element — card or lane header — toggles its selection (the ⌘-click analogue — a toggle, never plain click's replace, 04-interactions.md ▸ Selection; ruled 2026-07-29: a replace would silently wipe a multi-element selection, and one uniform VO-Space rule means the user never has to know the element kind to predict Space); ⌘↩ opens the card window; arrow keys and ⇧-arrows drive selection exactly as without VoiceOver. Selection state is always readable from the element (trait), and cut cards expose their dimmed pending state in the value ("cut, pending paste"). - **VO cursor and app selection are independent** (Finder-style): moving the VoiceOver cursor never mutates selection. VO-Space on any selectable element — card or lane header — toggles its selection (the ⌘-click analogue — a toggle, never plain click's replace, 04-interactions.md ▸ Selection; ruled 2026-07-29: a replace would silently wipe a multi-element selection, and one uniform VO-Space rule means the user never has to know the element kind to predict Space); ⌘↩ opens the card window; arrow keys and ⇧-arrows drive selection exactly as without VoiceOver. Selection state is always readable from the element (trait), and cut cards expose their dimmed pending state in the value ("cut, pending paste").
- **Actions come from the context menu.** Context menus are the single inventory of per-item actions (Open, Rename, Delete, Delete Immediately, width stepper, …), reachable the standard way (VO-⇧-M); where SwiftUI additionally surfaces menu items as custom accessibility actions, that's free improvement, not a separate design surface. **The custom-action cut** (confirmed 2026-07-29): every plain button row of an item's context menu becomes a custom action; rows that open their own accessible surface (Style…'s popover) and non-action controls (the quick-style swatch picker) stay menu-only — the context menu remains the full inventory either way. Each action must call the same method as its menu row, so the two surfaces cannot drift. - **Actions come from the context menu.** Context menus are the single inventory of per-item actions (Open, Rename, Delete, Delete Immediately, width stepper, …), reachable the standard way (VO-⇧-M); where SwiftUI additionally surfaces menu items as custom accessibility actions, that's free improvement, not a separate design surface. **The custom-action cut** (confirmed 2026-07-29): every plain button row of an item's context menu becomes a custom action; rows that open their own accessible surface (Style…'s popover) and non-action controls (the quick-style swatch picker) stay menu-only — the context menu remains the full inventory either way. Each action must call the same method as its menu row, so the two surfaces cannot drift.
- **Rotor**: lane titles are headings, so the headings rotor jumps lane-to-lane — on a one-dimensional board that *is* structural navigation; no custom rotors unless practice shows the need. **The title doubling is accepted** (ruled 2026-07-29): entering a lane reads the container label ("Doing, lane, 3 cards") and then the heading ("Doing, heading") — two elements, two purposes: the label gives boundary-crossing context, the heading feeds the rotor. This is the platform-standard landmark-plus-heading pattern; collapsing it would cost the on-entry announcement, the more valuable half. - **Rotor**: lane titles are headings, so the headings rotor jumps lane-to-lane — on a one-dimensional board that *is* structural navigation; no custom rotors unless practice shows the need. **The title doubling is accepted** (ruled 2026-07-29): entering a lane reads the container label ("Doing, lane, 3 cards") and then the heading ("Doing, heading") — two elements, two purposes: the label gives boundary-crossing context, the heading feeds the rotor. This is the platform-standard landmark-plus-heading pattern; collapsing it would cost the on-entry announcement, the more valuable half.
- **Trash lane**: when shown (View ▸ Show Trash), it is the last container, labeled as Trash with its count; toggling visibility is announced — "Trash shown" / "Trash hidden" (confirmed 2026-07-29: resulting state, not the action, so a mis-hit tells the user where the board ended up and the toolbar toggle reads like the menu checkmark; the count stays on the container label, which VO reads on arrival, rather than duplicated into the announcement). Its cards are ordinary card elements (resettled 2026-07-28 — the materialized trash: no lane entries, no special states beyond the container) exposing Delete and Reveal in Finder via the context menu; there is no Open (03-board-ui.md's no-editing-in-the-trash), and restore is drag-out or the ⌘X/⌘V keyboard path; keyboard reachability follows 04-interactions.md ▸ The trash. - **Trash lane**: when shown (View ▸ Show Trash), it is the last container, labeled as Trash with its count; toggling visibility is announced — "Trash shown" / "Trash hidden" (confirmed 2026-07-29: resulting state, not the action, so a mis-hit tells the user where the board ended up and the toolbar toggle reads like the menu checkmark; the count stays on the container label, which VO reads on arrival, rather than duplicated into the announcement). Its cards are ordinary card elements (resettled 2026-07-28 — the materialized trash: no special states beyond the container) exposing Delete and Reveal in Finder via the context menu; there is no Open (03-board-ui.md's no-editing-in-the-trash), and restore is drag-out or the ⌘X/⌘V keyboard path; keyboard reachability follows 04-interactions.md ▸ The trash. A trashed **lane** (rejoined 2026-07-29) is one flattened opaque element — "⟨title⟩, deleted lane, N cards" — never a container: its cards are not in the tree (the opaque unit, 03 ▸ Trash), its actions are the same Delete / Reveal in Finder, and its restore paths are the same drag-out or ⌘X/⌘V.
- **Search**: filtered-out cards leave layout and the accessibility tree together — the filter is the single source of truth for "what's on the board" (04-interactions.md), and the tree is one of its readers. Lane counts announced reflect the filter. - **Search**: filtered-out cards leave layout and the accessibility tree together — the filter is the single source of truth for "what's on the board" (04-interactions.md), and the tree is one of its readers. Lane counts announced reflect the filter.
## Moving without dragging ## Moving without dragging
+2 -2
View File
@@ -5,7 +5,7 @@ The undo/redo substrate for base Lanework (12-editions.md), filling the one gap
## Rules ## Rules
- **One stack per board, owned by the board session.** Not per-window: every window over a board (board window, its card windows) shares the store and shares the stack. `window.undoManager` for board surfaces returns the session's manager; 06 ▸ Undo routing applies unchanged — text-editing surfaces get their session-scoped text undo, everywhere else ⌘Z/⇧⌘Z hit the board stack. Undo is board-local, exactly as git undo was. - **One stack per board, owned by the board session.** Not per-window: every window over a board (board window, its card windows) shares the store and shares the stack. `window.undoManager` for board surfaces returns the session's manager; 06 ▸ Undo routing applies unchanged — text-editing surfaces get their session-scoped text undo, everywhere else ⌘Z/⇧⌘Z hit the board stack. Undo is board-local, exactly as git undo was.
- **Registration at the Writer boundary.** Every app-mediated mutation already passes through the Writer as a `WriteOperation` (02-architecture.md) — that closed enum is the exact inventory of undoable operations. Each Writer call site registers the inverse operation, computed from the pre-write snapshot the store already holds: move → move back (original lane, original `order`); reorder → restore original `order`; rename → restore title; restyle → restore prior style; resize → restore prior width; Edit-session body save → restore prior body bytes; card delete (⌫) → move back out of `.trash/`; restore-by-move → move back in; lane delete → recreate the folder from the registered inverse; create → remove the created folder. - **Registration at the Writer boundary.** Every app-mediated mutation already passes through the Writer as a `WriteOperation` (02-architecture.md) — that closed enum is the exact inventory of undoable operations. Each Writer call site registers the inverse operation, computed from the pre-write snapshot the store already holds: move → move back (original lane, original `order`); reorder → restore original `order`; rename → restore title; restyle → restore prior style; resize → restore prior width; Edit-session body save → restore prior body bytes; card or lane delete (⌫) → move back out of `.trash/` (lanes rejoined the trash 2026-07-29 — the recreate-from-capture inverse retires with the last destructive delete); restore-by-move → move back in; create → remove the created folder.
- **What is not undoable** (settled): **Permanently delete** (Delete Immediately, Empty Trash) — `purgeIsUnrecoverable` stays true in base, and the existing confirmation rule (03-board-ui.md) already fires on all base boards, since none have git history: the confirm *is* the safety. **Repair** (01-storage-format.md's duplicate-id remint) — undoing a remint would recreate the duplicate id the operation exists to remove. Both match their existing "destructive, confirmed, final" posture. - **What is not undoable** (settled): **Permanently delete** (Delete Immediately, Empty Trash) — `purgeIsUnrecoverable` stays true in base, and the existing confirmation rule (03-board-ui.md) already fires on all base boards, since none have git history: the confirm *is* the safety. **Repair** (01-storage-format.md's duplicate-id remint) — undoing a remint would recreate the duplicate id the operation exists to remove. Both match their existing "destructive, confirmed, final" posture.
- **Coalescing follows commit granularity** (settled): one gesture, one undo step — a multi-card move is one step with a plural title; an Edit session is one step, registered at the Edit→Preview flip (the effective Save — 05-card-window.md); a styling batch is one step (03's one-gesture-one-commit rule, substrate swapped). The 06 vocabulary supplies menu titles ("Undo Move 3 Cards"), via NSUndoManager's dynamic retitling — the same naming machinery both editions use. - **Coalescing follows commit granularity** (settled): one gesture, one undo step — a multi-card move is one step with a plural title; an Edit session is one step, registered at the Edit→Preview flip (the effective Save — 05-card-window.md); a styling batch is one step (03's one-gesture-one-commit rule, substrate swapped). The 06 vocabulary supplies menu titles ("Undo Move 3 Cards"), via NSUndoManager's dynamic retitling — the same naming machinery both editions use.
- **Session-only persistence** (settled): the stack lives with the board session and dies at close/quit — standard macOS behavior. Git undo's survive-relaunch property is a Pro difference, stated honestly (12's matrix). - **Session-only persistence** (settled): the stack lives with the board session and dies at close/quit — standard macOS behavior. Git undo's survive-relaunch property is a Pro difference, stated honestly (12's matrix).
@@ -15,7 +15,7 @@ The undo/redo substrate for base Lanework (12-editions.md), filling the one gap
## Interaction with the trash ## Interaction with the trash
⌫'s undo is the move back — a card delete is a move into `.trash/` (resettled 2026-07-28), so its undo is the ordinary inverse move, returning the card to its source lane and rank; a restore-by-move undoes the same way in reverse. The stack and the trash never conflict — they are the same folder moves addressed by recency instead of by selection. A **lane delete's undo recreates the folder** from the registered inverse (the one destructive delete — in-session only, which is the accepted net; git boards additionally keep it in history). A **permanent card delete registers no step** — Delete Immediately and Empty Trash are not undoable (Rules above); the confirm is the safety. ⌫'s undo is the move back — a delete is a move into `.trash/` (cards resettled 2026-07-28; lanes rejoined 2026-07-29), so its undo is the ordinary inverse move, returning a card to its source lane and rank, a lane to its strip position (subtree intact — it never left the folder); a restore-by-move undoes the same way in reverse. The stack and the trash never conflict — they are the same folder moves addressed by recency instead of by selection. The old lane-delete recreate-from-capture inverse is **retired** — no destructive delete remains outside the trash, so nothing needs byte capture. A **permanent delete registers no step** — Delete Immediately and Empty Trash are not undoable (Rules above), lanes and their freight included; the confirm is the safety.
## Out of scope ## Out of scope