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:
@@ -73,6 +73,7 @@ Body: board description (free Markdown).
|
||||
|---|---|---|---|
|
||||
| `order` | number | yes | Rank among lanes, ascending = left-to-right |
|
||||
| `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.
|
||||
|
||||
@@ -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**:
|
||||
|
||||
- **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 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).
|
||||
- **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.
|
||||
- **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.
|
||||
- **`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, 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.
|
||||
- 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
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
- **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.
|
||||
- **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).
|
||||
- **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, 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.
|
||||
- **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 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.
|
||||
- **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.
|
||||
- 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).
|
||||
- **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 — 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 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 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.
|
||||
- **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
@@ -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 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.
|
||||
- **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.
|
||||
@@ -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.
|
||||
|
||||
- **⌥⌘↑/⌥⌘↓ 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).
|
||||
- **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.
|
||||
|
||||
### 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.
|
||||
- **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.
|
||||
- **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 — 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 — 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 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 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…, 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 — 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)
|
||||
|
||||
|
||||
@@ -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).
|
||||
- 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: **`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.
|
||||
|
||||
@@ -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").
|
||||
- **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.
|
||||
- **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.
|
||||
|
||||
## Moving without dragging
|
||||
|
||||
@@ -5,7 +5,7 @@ The undo/redo substrate for base Lanework (12-editions.md), filling the one gap
|
||||
## 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.
|
||||
- **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.
|
||||
- **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).
|
||||
@@ -15,7 +15,7 @@ The undo/redo substrate for base Lanework (12-editions.md), filling the one gap
|
||||
|
||||
## 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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user