2026-07-30 design pass (with the user), superseding the 2026-07-27 two-app split and the 2026-07-29 App Group ruling. Rulings: - One Mac App Store app (dev.rzen.indie.Kanban, free, 2.0 updates the existing record); Lanework Pro = auto-renewable subscription (StoreKit 2) unlocking the git tier; libgit2 compiles in dormant. The KanbanPro target, module-alias tests, verify-editions, UTI/About/Types twins all retire. Teams stays deferred — probably a separate app, and never on a shared app group. - No grandfathering: 1.x's free git users get the inert-.git posture; unsubscribed and lapsed are one state — histories freeze, resume on subscribe via the committer's catch-up commit. - Entitlement = local read of StoreKit's signed transaction store at board-session composition; never a network call on the open path; subscribe effective at next open (purchase flow offers reopen); lapse never interrupts an open session; expiry-while-offline with auto-renew on holds in grace until a real refresh. - App Group removed wholesale: registry, clipboard staging, template store, defaults return to ordinary Application Support; grant slots collapse to one bookmark, open-now to one flag; freshness stamp, pid-liveness line, group-id provisioning risk all dissolve. - Quiet signposts carry over; Settings gains the Pro section (subscribe/ manage/restore) as the third named Pro surface. 12-editions.md rewritten as the tiers doc; edition vocabulary swept to tiers across 00-13, README, WISHLIST. Claude-Session: https://claude.ai/code/session_01CqjXB7ASoWtbyoGod68k97
38 KiB
Interactions
Selection, drag & drop, keyboard, clipboard, search. This is where the old app spent most of its polish budget; nearly everything here is carried over as settled — the details below are the contract, not suggestions.
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 — 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) — which extends the full column height below the last row, card and lane rows alike (re-affirmed 2026-07-29; the rewrites dropped the clause, the ruling never changed): no dead zone, a band can arm from anywhere in the shown trash's column.
- 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.
- Clicking never edits (pivot from the pathfinder's Finder-rename two-stage click): one click selects, and that is all a single click ever does — no slow-second-click rename, no timers, no accidental edit on a hesitant click. Inline rename is Return on a sole selected card, or Board ▸ Rename — the menu item is a lane's only rename path, since Return on a lane creates a card (Grammar below). A fast double-click opens the card window (⌘↩'s pointer twin). Committing an empty rename on an existing item removes its
titlekey (titles are optional; the face shows the untitled placeholder).
Drag & drop
- Shadow placeholder with live reflow: dragging shows a shadow at the exact landing spot; the board reflows to make room. The whole slot is the trigger zone; proposals are geometry-based so the shadow is stable, never jittery. Width-aware triggers for lanes (no reflow until the cursor reaches where the dragged lane would actually land); the shadow holds until a real new candidate appears. (The old repo's DRAG-REORDER.md documents the full model — port that document alongside the implementation.)
- Cards reorder within a lane and move between lanes (folder move). Lanes reorder; a full-size replica travels under the cursor.
- Multi-drag: dragging any member of a multi-selection drags the whole selection; N contiguous shadows; drop inserts contiguously in preserved relative order — defined, for any multi-selection, as lane
orderfirst, then cardorder(a cross-lane selection flattens left-to-right, top-to-bottom). - Locality picks the default — the Finder volume model (settled): within a board a drag is a move (rearranging); between boards it is a copy (transferring — the system copy badge shows over the foreign board). ⌥ always forces copy and ⌘ always forces move, Finder's exact modifier grammar; each is a no-op where its behavior is already the default. The badge tracks the effective operation live as the cursor crosses a board boundary.
- Within-board ⌥-drag copies: originals stay, cursor shows the copy badge, fresh-GUID duplicates land at the drop. Lane drags never copy within their board — a within-board lane duplicate is not available by drag (⌥ is simply ignored there: the drag stays a clean reorder and the badge never shows copy); the duplicate itself is supported, via the clipboard (Lane paste below) — the usual shape: the keyboard path is the canonical one, drag the enhancement (10-accessibility.md).
- Cross-board copy (the default): cards and lanes (including multi-selections) drag between open boards; fresh-GUID duplicates land at the drop, originals stay,
createdis kept (a copy is a fork — 01-storage-format.md). Lanes copy cards and all — transferring workflow structure between boards is safe by default. A lane carries exactly its cards — the trash is board-level (.trash/— 03-board-ui.md), so there is nothing lane-nested to strip or carry: copy and ⌘-drag move alike transfer the lane's folder as it is (resettled 2026-07-28; the old tombstone-stripping rule is retired with the tombstone model). - Cross-board move (⌘-drag): a real filesystem move, works across volumes — identity travels. A moved folder whose UUID already exists in the destination board arrives as a fresh-UUID copy (01-storage-format.md's import-boundary rule); in a compound move (lane with cards, multi-selection) only the colliding folders are reminted — the rest is a true move (01's per-folder degradation).
- Files from Finder: dropped on a card → copied into its
attachments/(any file type, multi-file; card highlights while hovered). Dropped on lane empty space → creates a card with the file attached, titled with the filename without its extension (multi-file drop: one card per file). Folders are refused at hover (settled — the attachment model is flat top-level files, and the importer refuses directories by design): a drag containing only folders never engages — no highlight, no drop proposal, the standard incompatible-payload read; a mixed drag proposes for its files only, and the drop imports the files while a loss row (02-architecture.md's warning tone) names the skipped folders ("Folders can't be attached — 2 skipped"). The create path thereby only ever fires with at least one importable file — no card is minted for an import that cannot succeed. Created cards land at the drop position (settled): resolved through the same card-grid zones an ordinary card drag uses, shadow included — drops are positional everywhere, and append-at-bottom stays the creation trio's rule, not the drop's. A multi-file drop shows one nominal-height shadow per incoming file (the multi-drag precedent; when macOS withholds item counts during hover the count floors at one shadow, the commit unaffected). A release on the lane header resolves to the topmost position (settled — forgiving beats a dead stripe: the header's chrome roles don't collide with a file payload). The landing shadow is the create path's whole feedback (settled): no lane-level highlight on top — each target gets one clear signal, and the card-attach highlight exists precisely because that target has no shadow. - A foreign reload mid-drag re-grounds the drag, never corrupts the drop (settled — a two-second drag racing agent edits is the designed concurrency). Three rules compose: (1) geometry re-derives — the frozen-at-drag-start inputs are the dragged items' sizes and the physical pointer only (03-board-ui.md ▸ Motion); the analytic resting zones recompute against each new snapshot, so a foreign lane-count re-divide mid-drag just moves the zones and the next proposal targets the board as it now is. (2) Proposals re-validate by liveness — a proposal whose target lane vanished in the reload is invalidated; the shadow withdraws and no proposal stands until the pointer reaches a live target, and release with no valid proposal cancels — items return, nothing is written; a card is never filed under a vanished parent. (3) An emptied drag cancels itself — drag membership is already a UUID set that vanished items leave silently (02-architecture.md); when the last dragged item vanishes the replica dissolves and release is a no-op. Partial vanishing drops the survivors, matching the pending-cut precedent. A cross-board lane arrival pre-divides the destination strip during hover (settled): while a foreign lane drag proposes into a board, the destination's standard width is computed with the arriving run's units included, so the shadow draws at the width the lane will actually take — without this it overflows the strip (the pathfinder's stripWidthUnits). The first entry samples the un-widened standard for one frame before hysteresis settles — accepted, imperceptible.
Clipboard
- ⌘X/⌘C/⌘V on cards and lanes (resettled — lanes joined the clipboard so cross-board structure transfer has a keyboard path under the every-function contract; the cards-XOR-lanes selection rule means the clipboard holds cards or lanes, never both). Hybrid clipboard: pasteboard carries a JSON manifest + plain text; full folder snapshots staged app-side (02-architecture.md ▸ Per-board app state's app-wide home) so paste reproduces the item byte-for-byte — cards, attachments and all — across boards. Each manifest entry embeds the full
index.md— identification metadata (menu validation, refusal wording, the plain-text flavor's source), never a materialization source since the 2026-07-29 refuse-don't-degrade ruling below (a lane entry embeds its cards' too). Staging lifecycle (settled): snapshots are staged eagerly at ⌘C/⌘X time — copy captures the source as it is at the gesture, immune to later deletion or unmount — and the store holds at most the current copy: a new Lanework copy replaces the previous snapshot, and a sweep at launch and on each copy purges entries the pasteboard no longer references (another app taking the pasteboard orphans the snapshot; the next sweep collects it). The snapshot survives relaunch exactly as long as the pasteboard still points at it — a copy made before quitting pastes whole after restart. A paste is an import boundary, so normalization applies (settled 2026-07-28 — 01-storage-format.md's loose-file rule): loose files the staged snapshot carries beside a card'sindex.mdland in the pasted card'sattachments/, Finder-renamed on collision — nothing the snapshot preserved is dropped on arrival. A paste whose staged snapshot is missing or unreadable refuses loudly — never degrades (re-ruled 2026-07-29, retiring the degraded embedded-index.mdfallback and its loss row; Finder's invariant adopted, and 01's leniency doctrine applied — proceed-partially-lose-a-little is never a verdict): the paste produces nothing, and a one-shot failure banner names it from the manifest's metadata ("The copied cards are no longer available" / "Couldn't paste 'Fix login' — the copied content is gone"; BannerCenter owns the phrasing). An item arrives whole — index, attachments, loose files, and comments when they ship — or not at all; a hollowed card is never materialized, so the loss-accounting problem (what didn't arrive, and whether the totals are honest) dissolves rather than being solved. The refusal is transactional — all-or-nothing for the whole paste, the copies-are-transactions posture (01). With eager staging and the shared-store sweep discipline this is a rare corner, not a flow: the refusal names it, and ⌘C again is the recovery. The pasteboard is re-read lazily, and the brief lie is accepted (settled): changeCount is checked on activation, on menu validation, and before paste — no timers; a background app taking the pasteboard while Lanework stays frontmost can leave Edit ▸ Paste enabled until the next check, and the paste itself re-validates and no-ops — nothing stale ever lands, which is the guarantee that matters. - Cut is Finder-style deferred: cut items dim in place until paste moves them; voided if another app takes the pasteboard or the source board closes; second paste materializes copies. Deletion voids per item: a cut item that is deleted (moved to the trash or destroyed) or vanishes externally before paste drops out of the pending cut — 02-architecture.md's UUID-set rule; transient state never resurrects what's gone — so paste moves only the survivors, and a cut voided down to nothing is simply void (paste disabled, no error).
- Paste lands after the anchor card (or appends to a selected lane); a multi-selection anchors at its last member in flatten order — the ⌘N target rule's shared anchor (The map below). Copies keep
created(a duplicate is a fork) and take fresh GUID/order/modified. A trash selection never anchors paste (settled — the ⌘N target rule's own wording, returned to the precedent it cites): ⌘V stays enabled and behaves exactly as with nothing selected — a card payload appends to the last-active lane, a lane payload lands at the board's right end; the trash is never the destination (▸ The trash), and a trashed card's live disk-lane never leaks in as "the selected card's lane". - Lane paste lands after the anchor lane — the selected lane, or the selected card's lane (several selected: the last, per the shared anchor rule); nothing selected = the board's right end. Semantics mirror the drag pair above exactly: a pasted copy takes fresh GUIDs throughout; a cut-paste is the ⌘-drag move — the folder moves whole (nothing lane-nested to strip or carry — the trash is board-level, resettled 2026-07-28). Pasting into the source board is supported and is the within-board lane duplicate (settled): fresh GUIDs apply as anywhere else, no menu-validation special case — the drag path deliberately lacks this operation (⌥ ignored on lane drags, above), the clipboard is its one home.
Keyboard
Keyboard navigation is a first-class UX concern — the board must be fully operable without the mouse: creating, navigating, renaming, moving, deleting, and opening items all need keyboard paths, and every board function must have a menu item — the menu home is what makes a function keyboard-reachable (full keyboard access) and remappable (Configurable bindings below) — with a default chord where one earns its keyspace: 11-command-nexus.md's "— (no default)" rows are deliberate, not gaps, and remap like any other (toolbar-hosted functions included — toolbars are pure enhancement, 03-board-ui.md ▸ Toolbar). The pathfinder covered creation, navigation, rename, and delete; the keyboard-map iteration's outcome — settled as a coherent whole — is inventoried in 11-command-nexus.md, with its behavioral rules below.
Grammar (fixed keys — deliberately not remappable)
- Arrows: spatial card navigation (nearest card in the direction, across interior grid columns and lanes); with a lane selected, ←/→ move lane selection; ⇧-arrow extends — except ⇧↑/⇧↓ in the lane domain, which are inert (settled: there is nothing above the lane domain and no vertical range within it); selection scrolls into view; all grammar keys inert while a title editor is focused, and menu dispatch narrows to the text domain (focused-editor rule below).
- ⌥-arrows jump: ⌥↑/⌥↓ to the current lane's first/last card; ⌥←/⌥→ to the first/last lane. The horizontal jumps land on a card (settled — ⌥↑ is the keyboard's one entry to lane selection, so ⌥←/⌥→ never select the lane itself): the first card of the first/last non-empty lane, scanning inward past empty lanes; ⌥→ prefers the shown non-empty trash — its first entry — per the last-container rule (The trash below). ⌥↑ escalates into the lane domain (settled — the keyboard's one entry to lane selection): with the lane's first card already selected, ⌥↑ selects the lane itself — up in the hierarchy sense, the same key one press deeper; with a lane selected, ↓ (or ⌥↓) descends back into its cards at the first (last) card, and ⌥↑ is inert. An empty selection seeds at the first lane's first card on any plain arrow (deterministic origin; the ⌥-jumps behave as specified regardless) — two ⌥↑ presses from nothing reach the lane domain.
- Return on a selected lane: creates a card at its bottom, editor focused; Return commits and re-selects the lane (next Return = next card); ⌘↩ commits and opens the card window. Abandoned placeholders (Escape, empty commit, click-away) are discarded — creating-then-abandoning never leaves an empty card behind (untitled cards exist only when made deliberately, e.g. by an external writer or by clearing an existing title). The placeholder is store-transient overlay state — the named exception to 02-architecture.md's one-way flow; nothing exists on disk until the title commits.
- Inline rename tracks its target by UUID, and vanishing discards it (the placeholder and card-window kin rules — 02-architecture.md — applied to the third inline editor): a foreign move mid-rename is invisible — the editor follows the UUID and the commit writes the title wherever the card now lives; a target that is trashed, deleted, or gone at commit time discards the editor and its keystrokes silently (entering the trash is a vanish from the board; nothing is ever written into a vanished folder). A write that fails after a valid commit is the ordinary one-shot write-failure banner. VoiceOver announces the vanished target per 10-accessibility.md's recovery rule.
- Return on a sole selected card: inline rename. Return disambiguates on card selection — sole card = rename, lane = create (above) — and is inert on a multi-card selection; a lane's rename path is Board ▸ Rename. Escape steps outward one layer per press: abandons an open editor; else clears search, returning focus to the board (Search below); else clears the selection — the keyboard deselect.
- Focused editor = text domain (settled): while an inline title editor — rename or the new-card placeholder — is focused, board-scoped menu commands (Delete, New Card, Paste, Move, Style, …) disable via menu validation; text-domain chords route to the field as standard text ops — ⌘Z/⇧⌘Z are the editor's text undo (06-history-undo.md ▸ Undo routing), ⌘X/⌘C/⌘V/⌘A act on the text. The one board-command carve-out is Open Card ⌘↩, which stays enabled to commit the edit — placeholder or rename — and open the card window. Exits are otherwise unchanged: Return commits, Escape abandons; click-away splits by editor kind — a rename commits (focus loss = commit, matching the card window's title field in 05-card-window.md and the branch-switch parenthetical in 06-history-undo.md), while the placeholder discards per its rule above, the deliberate exception because nothing exists on disk yet.
- Caret chords yield to any focused text control (settled): Board ▸ Move Left/Move Right ⌘←/⌘→ and the width pair ⌥⌘←/⌥⌘→ disable via menu validation whenever any text control has keyboard focus — inline title editors, the board search field, board-popover fields (rename, git identity, remote), and card-window fields — because an enabled menu key equivalent fires before the field ever sees the key, and ⌘←/⌘→ are the standard line-start/end caret chords. Caret motion always wins in text (the Safari pattern: ⌘← is Back, yet moves the caret while a field is focused); the lane commands re-enable the moment focus returns to the board. This is a narrow, per-command broadening of the focused-editor rule, not a general one: board commands whose chords carry no text meaning keep their surface-specific dispatch — in particular the search field's board-commands-stay-enabled rule (Search below) — and the search field's explicitly ruled ⌘⌫ steal (File ▸ Delete, not delete-to-line-start) stands.
- ⌫ on a selection: delete — the plain-key synonym for File ▸ Delete ⌘⌫, staged by place like the menu item (see The map). Grammar, not a menu item: giving it a menu home would require a second "Delete"-titled item, which would collide for title-matched remapping (Configurable bindings). Inert while a title editor is focused, like every grammar key.
- The card window speaks the same grammar: Return in Preview enters Edit, Escape returns to Preview (05-card-window.md) — plain keys, not menu items.
- These plain-key behaviors are platform grammar (Finder's own Return/arrows aren't remappable either) and sit below the remapping mechanism, which handles modifier chords on menu items only — see Configurable bindings.
The map — moved to the command Nexus
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; 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: lanestamped 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 cardorder, 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 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; 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)
Custom shortcuts are system-native, with no in-app remapping UI: macOS's App Shortcuts mechanism (System Settings ▸ Keyboard ▸ App Shortcuts, stored as NSUserKeyEquivalents in the app's defaults) remaps any menu item, and AppKit applies it automatically — menus always display the effective binding, so the menu bar is the self-documenting keyboard map. Because every board function is a menu item (the contract above), coverage is complete for all modifier-chord commands; the fixed grammar keys stay fixed by design. An in-app shortcut-recorder pane was considered and set aside as ceremony (../WISHLIST.md); the Help content carries one line teaching the System Settings path. Constraints this mechanism imposes, adopted as design rules:
- Menu item titles are API. The mechanism matches on exact titles — renaming a menu item orphans users' bindings. Titles change only with the deliberateness of a schema change.
- Toggles keep one stable title with a checkmark state — "Show Trash" stays "Show Trash" when checked, never becomes "Hide Trash". (Same for Edit Body and Raw Source.)
- Undo/Redo are effectively not remappable — NSUndoManager rewrites their titles dynamically ("Undo Move Card…"), which defeats title matching. Accepted; nobody remaps ⌘Z.
- Two items may share a default chord when validation is mutually exclusive (a pattern currently unused — Put Back's retirement removed its one instance) — AppKit fires the enabled one. Each keeps its own stable title, so remapping stays per-item. Corollary: no two menu items share a title either (titles are the remap key), which is why plain-⌫ delete is grammar rather than a second Delete item.
Accessibility
Stance (committed, 00-vision.md): accessibility is a requirement, not an afterthought. The keyboard-first contract above is doing double duty as the drag-free operation guarantee — every function reachable without the mouse is the accessibility floor. The lane-move defect 10-accessibility.md named is closed by the map above (Move Left/Right on a selected lane). The full design — VoiceOver tree shape and logical traversal order over the masonry, clipboard as the drag-free card-move story, live-reload announcements, text scaling, visual accommodations, and verification — is 10-accessibility.md.
Search
- Search field invoked with ⌘F (the board toolbar's sole default item; removed from the toolbar, ⌘F surfaces it transiently — 03-board-ui.md ▸ Toolbar; in the card window, Edit ▸ Find is find-in-text instead — 05-card-window.md), live filter: cards whose title and body both miss the query animate out; case/diacritic-insensitive substring. Scope is all card content the format makes meaningful (re-ruled 2026-07-29, superseding title-plus-body-only): title + body today; comment bodies join when comments ship — via a search-owned transient comment index, never the snapshot: the first live-query keystroke kicks an async sweep of
comments/*/index.mdbodies (.draftandcomments/.trash/excluded), kept fresh by the same FSEvents stream while a query is active and discarded when it clears — the board walk stays O(cards), 01's window-scoped read untouched; attributes join as they activate (title now; labels/tags and their kin are reserved, inert keys this version — nothing to search until a future version gives them life). Attachment filenames stay unsearched. Scope options (content vs attributes, either/or) are WISHLIST #10. - The filter is the single source of truth for "what's on the board": layout, drop zones, marquee, ranges, arrow nav, and lane count badges all read it. Hidden cards leave the selection; creating a card clears the search — creation's carve-out exists because a brand-new card must not be born invisible, and it is stated by mechanism, not by gesture (settled): any user-initiated creation on the board clears the query — ⌘N, Return-creation, the header button, empty-space double-click, paste, and Finder file drops alike — while foreign/agent-filed cards keep riding the live filter (02-architecture.md's derived-result rule). Rename deliberately gets no carve-out: a rename committed during an active search re-runs the predicate like any edit — a title that stops matching animates the card out and drops it from the selection, exactly as an agent's edit would; the filter stays a pure predicate with one exception, not two. Escape is staged (settled): in a non-empty field it clears the query, focus staying in the field; in an empty field it returns focus to the board; with board focus and an active search, one press clears the search and the full board returns — search takes Escape before its clear-selection meaning, which applies only when no search is active. A lane the query empties keeps its slot (settled): lanes are never filtered out — an all-misses lane stays on the board at its width with a 0 badge (the count reads the filter, 03-board-ui.md); the search filters cards, and the board's structure is not a search result. A leaving card stays input-reachable for its out-transition (settled): marquee and arrow targets deregister when the ~0.28 s animate-out ends, so a card mid-departure is briefly reachable while already out of the selection — accepted: it is literally on screen for that span, and closing the window would teach three input sites a predicate the layout already applied. An open inline rename survives the filter hiding its card (settled): the editor is a surface the filter doesn't reach — it stays open and focused, commits by UUID wherever the card lives, Escape abandons; keystrokes are never silently discarded for a card that still exists (the dirty-buffer courtesy), and the vanish-discard rule stays reserved for true liveness flips. The typed-query path can't even occur — focusing the search field is focus loss, which commits the rename first — so the rule covers foreign edits that stop the card matching.
- Dispatch while the search field is focused (settled): the field is a control, not a content editor — the focused-editor lockdown (Grammar above) does not apply. Text-domain keys route to the field: ⌘A/⌘X/⌘C/⌘V act on the query, plain ⌫ edits the query and never reaches the board, horizontal arrows move the caret. Every key with the field focused acts on the field — stock NSSearchField behavior, no pass-throughs (settled): vertical arrows are caret movement, ⇧-arrows select query text, and Return is a swallowed no-op (the filter is live, there is nothing to submit — it never reaches the board's rename/create grammar). Tab is the keep-filter path: plain key-view traversal moves focus to the board with the query intact, and the whole board grammar (arrows, ⌥↑ escalation, Return, ⌘↩) then applies over the filtered board; ⌘F returns to the field. Board menu commands stay enabled and act on the board selection exactly as when the field is unfocused — ⌘N included (creating a card clears the search, above) — except the caret-chord commands: Move Left/Right ⌘←/⌘→ and the width pair ⌥⌘←/⌥⌘→ disable while the field is focused (Grammar above, caret-chords rule), so ⌘←/⌘→ stay line-start/end in the query even with a lane selected — and the Delete pair stays unambiguous by construction: plain ⌫ is query editing, ⌘⌫ is File ▸ Delete on the selection, and ⌘Z/⇧⌘Z are the field's own text undo, never git undo (06-history-undo.md ▸ Undo routing's control-class rule).
Changes from Kanban
- Finder file drop on lane empty space (old item 49) is in scope from the start rather than a follow-up.
- Clicking never edits: the pathfinder's Finder-style slow-second-click inline rename is gone — Return (or the menu) renames, double-click only opens. One less timer, and no edit ever starts from a hesitant click.
Open questions
None currently — the keyboard map is settled (⌥⌘↑/⌥⌘↓ within-lane sort, ⌘←/⌘→ lane moves, ⌥-arrows jump, creation trio, ⌘N target rule, system-native remapping); its inventory lives in 11-command-nexus.md, its behavior above.