Remove the lane header's "+" new-card control
The lane header no longer draws a new-card button. Every other creation path is untouched: File > New Card (Cmd-N), Return on a selected lane, and double-click on lane empty space. - LaneView: drop the button and its overlay slot, leaving the collapse chevron as the header's one piece of trailing chrome. - NewCardTarget: drop the button's click-names-target carve-out over the Cmd-N target rule (Return on a selected lane is now the only direct-target path left). - AccessibilityPhrases: drop the button's spoken label. - BoardMetrics: drop its width reserve; laneHeaderTrailingReserve now reduces to the collapse chevron's own reserve. - DESIGN/03, 04, 10, 11: update the lane title-bar inventory, the Cmd-N override clause and search-clearing mechanism list, the accessibility tree-shape sentence, and the pointer-grammar row that described the button. - Tests: drop the two pinned accessibility-label tests and the newCardButtonReserve assertions; retarget the truncation-headroom test at the now-solo laneHeaderTrailingReserve; fix stale doc-quote comments and the manual accessibility-verification checklist. Full KanbanTests suite: 2807 tests, 482 suites, all passing. Claude-Session: https://claude.ai/code/session_014PtZdPwqZuqEDLc6wZMtEy
This commit is contained in:
@@ -18,10 +18,10 @@ Toolbars are **pure enhancement**: every function they host already has a menu i
|
||||
|
||||
## Lane
|
||||
|
||||
- Title bar: leading SF Symbol (the lane's `icon`), title, **card-count badge** (quiet, secondary styling), new-card button. The whole title bar is the lane's drag surface — no separate grip; a plain click (no movement) on it selects the lane (04-interactions.md ▸ Selection). **The lane has one context menu** (settled), invoked on the header or on lane empty space alike — Rename, Style…, the quick-style recents row, the Width stepper, Delete (inventory normative in 11-command-nexus.md ▸ Context menus); a full lane still has its header, so the menu is always reachable. The count reads the search filter like every other surface (04-interactions.md): during a search it shows the visible count, not the total.
|
||||
- Title bar: leading SF Symbol (the lane's `icon`), title, **card-count badge** (quiet, secondary styling), the collapse chevron (below). The whole title bar is the lane's drag surface — no separate grip; a plain click (no movement) on it selects the lane (04-interactions.md ▸ Selection). **No header card-creation control** (removed): the header carried a "+" new-card button through m5; it is gone, and every other creation path — ⌘N, Return on a selected lane, double-click on lane empty space — is unchanged. **The lane has one context menu** (settled), invoked on the header or on lane empty space alike — Rename, Style…, the quick-style recents row, the Width stepper, Delete (inventory normative in 11-command-nexus.md ▸ Context menus); a full lane still has its header, so the menu is always reachable. The count reads the search filter like every other surface (04-interactions.md): during a search it shows the visible count, not the total.
|
||||
- Body: vertical card stack (masonry grid when wide — settled, the pathfinder's masonry works), scrolls vertically.
|
||||
- Right-edge **drag-to-resize** between integer widths (1×, 2×, 3×, … — no cap): shadow snaps at the inter-column gap with 10pt release hysteresis; the window grows/shrinks by one standard width per snap so other lanes keep their exact size. **At the screen's visible frame the window stops and the drag degrades to the re-divide** (settled 2026-08-08, superseding the pathfinder's hard stop): further snaps re-divide the now-fixed width across one more unit each — siblings compress, exactly the stepper's mechanism — so a lane keeps growing at the siblings' expense once the screen is spent; a window with no headroom to begin with (already at the edge, or full screen) re-divides from the first snap. The two regimes meet without a pixel jump — the re-divided standard at the screen fit *is* the frozen standard. Shrinking mirrors it: re-divide back down to the screen fit, then window shrinking resumes (and shrinking is always allowed). The rubber-band feedback (the dragged edge gives a fraction of the overshoot and snaps back, signalling the bound) moves to the true end of travel — the strip's own capacity — and the window still never overflows the screen. The header context menu's Width control (stepper, uncapped) remains the precise control: it never touches the window, it **re-divides** the existing width across the new unit total (siblings compress); the drag now reaches those widths too, but the stepper stays the exact, accessible path. The **Increase/Decrease Lane Width menu items (⌥⌘→/⌥⌘← — 11-command-nexus.md) are this stepper's keyboard face** — same re-divide semantics, never the window's size; window-growing behavior belongs to the drag alone — and they **batch over a multi-lane selection** (settled, the styling precedent): each selected lane steps one unit, one gesture, one commit; the context-menu stepper itself stays single-lane by nature. **A width write landing on 1 removes the `width` key** (settled — the remove-at-default family: the empty rename removes `title`, the None well removes `background`): a default lane's frontmatter stays clean, drag, stepper, and menu items alike; a hand-written `width: 1` is legal and preserved until the app itself next edits width. **A failed width commit at drag release rolls the window back** (settled): the failure surfaces as the ordinary one-shot banner and the window animates back by the uncommitted delta — 02-architecture.md's write-failure honesty (the action visibly doesn't happen) applied to the one control that moves the window.
|
||||
- **Collapsed lanes** (settled 2026-08-08): a lane folds to a **fixed slim vertical strip** — its `icon`, its card-count badge, its title rotated a quarter turn **counterclockwise so it reads bottom-up** (the book-spine orientation) and **justified to the top** of the room below that chrome (ruled by the owner 2026-08-08, reversing the top-down reading of the first pass), and its accent band, at a fixed width (~44pt, an em multiple like every other figure) — and **the strip is not part of the width re-division**: the window's width divides across the *expanded* lanes' units alone, with each collapsed strip's fixed width taken off the top, so folding a lane is a re-divide trigger of the Show/Hide Trash family (the window never moves and the siblings grow into what the lane gave up). Persistence is a first-class lane frontmatter key, `collapsed: true` — **document state exactly like `width`**, because the files are the board and an agent must be able to fold a lane by editing frontmatter; absent means expanded, expanding **removes** the key (never writes `false` — the remove-at-default family), and the lane's `width` is preserved untouched while collapsed so expanding restores the lane the user had. **Toggling**: the header's trailing collapse chevron (always visible beside the new-card button — the header has no hover chrome to match), the lane context menu's single **Collapse Lane** / **Expand Lane** row (single-lane like the width stepper, not batched), and a plain click anywhere on the collapsed strip, which expands it; a modified click on the strip stays the ordinary selection grammar, so a folded lane is still selectable by pointer. Rename and the expanded header's double-click behaviours are unchanged. **While collapsed**: no resize handle (the width stepper and ⌥⌘→/⌥⌘← still edit the preserved key), and the lane's cards are **not rendered at all** — which is what excludes them from the marquee sweep, from spatial keyboard navigation, and from the ⌥-arrow jumps (↓ from a folded lane is inert, like an empty one), and what makes ⌘N skip the lane for creation (a selection inside a folded lane falls through to the last-active lane rather than refusing). A drop on the strip **appends at the lane's end**, like an end-of-lane drop, with an accent edge standing in for the shadow the strip has no masonry to open; there is no hover-to-auto-expand in v1 (future work). Lane reorder drag works on the strip, and a folded lane's replica and drop shadow are the strip rather than its units. **Search still matches cards in collapsed lanes** — the badge and the flatten lists count them, since collapse is a presentation fold rather than a filter — but the lane stays folded and shows no match: pointing into a folded lane is not something the current search presentation can do, and the honest minimum is that the query does not fold or unfold anything. The **trash quasi-lane is not collapsible**; multi-select styling and the Style editor are unaffected.
|
||||
- **Collapsed lanes** (settled 2026-08-08): a lane folds to a **fixed slim vertical strip** — its `icon`, its card-count badge, its title rotated a quarter turn **counterclockwise so it reads bottom-up** (the book-spine orientation) and **justified to the top** of the room below that chrome (ruled by the owner 2026-08-08, reversing the top-down reading of the first pass), and its accent band, at a fixed width (~44pt, an em multiple like every other figure) — and **the strip is not part of the width re-division**: the window's width divides across the *expanded* lanes' units alone, with each collapsed strip's fixed width taken off the top, so folding a lane is a re-divide trigger of the Show/Hide Trash family (the window never moves and the siblings grow into what the lane gave up). Persistence is a first-class lane frontmatter key, `collapsed: true` — **document state exactly like `width`**, because the files are the board and an agent must be able to fold a lane by editing frontmatter; absent means expanded, expanding **removes** the key (never writes `false` — the remove-at-default family), and the lane's `width` is preserved untouched while collapsed so expanding restores the lane the user had. **Toggling**: the header's trailing collapse chevron (always visible, not hover-revealed — the header has no hover chrome to match), the lane context menu's single **Collapse Lane** / **Expand Lane** row (single-lane like the width stepper, not batched), and a plain click anywhere on the collapsed strip, which expands it; a modified click on the strip stays the ordinary selection grammar, so a folded lane is still selectable by pointer. Rename and the expanded header's double-click behaviours are unchanged. **While collapsed**: no resize handle (the width stepper and ⌥⌘→/⌥⌘← still edit the preserved key), and the lane's cards are **not rendered at all** — which is what excludes them from the marquee sweep, from spatial keyboard navigation, and from the ⌥-arrow jumps (↓ from a folded lane is inert, like an empty one), and what makes ⌘N skip the lane for creation (a selection inside a folded lane falls through to the last-active lane rather than refusing). A drop on the strip **appends at the lane's end**, like an end-of-lane drop, with an accent edge standing in for the shadow the strip has no masonry to open; there is no hover-to-auto-expand in v1 (future work). Lane reorder drag works on the strip, and a folded lane's replica and drop shadow are the strip rather than its units. **Search still matches cards in collapsed lanes** — the badge and the flatten lists count them, since collapse is a presentation fold rather than a filter — but the lane stays folded and shows no match: pointing into a folded lane is not something the current search presentation can do, and the honest minimum is that the query does not fold or unfold anything. The **trash quasi-lane is not collapsible**; multi-select styling and the Style editor are unaffected.
|
||||
|
||||
## Card face
|
||||
|
||||
|
||||
@@ -55,7 +55,7 @@ Every command is a menu item. The full inventory — every command and action, i
|
||||
- **⌫/⌘⌫ 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, a mixed trash selection's alert counting both kinds). 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. **In the trash the successor walk is kind-blind** (ruled 2026-07-31): the next row of either kind, in the same all-rows order plain arrows walk — a successor is a fresh singleton selection, so the landing violates no grammar, and repeated ⌘⌫ empties a mixed trash without dead-ends, each delete confirm-gated per its kind. 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; containers re-ruled 2026-07-31 — the popover/sheet split — and **re-ruled back 2026-08-07**): form-like configuration keeps exactly one home per control, and that home is the **board popover** — rename, styling, branch display and *switching*, branch *creation*, add git, commit identity, ahead/behind with Pull/Push, the status badges; its keyboard path is Board Info (⌘I) plus Tab-reachable controls (10-accessibility.md's Full Keyboard Access). *(For a week, setup lived in a board settings sheet reached by Board ▸ Board Settings…; the split's mechanical argument — setup flows fire confirmation alerts, run inline network probes, and accept drag-in key import, acts that want a surface a stray click can't dismiss — did not survive the cost of a second validated surface for controls a user could otherwise reach directly. 03-board-ui.md ▸ Board settings sheet carries the retirement, and 07-sync-collab.md's credential and SSH surfaces are where the argument gets its next hearing.)* 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; 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)
|
||||
|
||||
@@ -85,7 +85,7 @@ Custom shortcuts are **system-native, with no in-app remapping UI**: macOS's App
|
||||
## 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.md` bodies (`.draft` and `comments/.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. **A drop under an active filter counts in the rendered space** (ruled 2026-08-06, closing the divergence found 2026-08-01): the zones, the shadow, and the write all resolve through the same filtered list — the position the shadow shows is the landing the write performs (the shipped interim — zones and write counting the unfiltered lane while the slots rendered filtered — was self-consistent but named a position the user could not see; retired). The slot's meaning in the full order is its **visible anchor**: the dropped card enters immediately *after* the slot's visible predecessor — or immediately *before* its visible successor when the slot has none (top of the lane) — and a lane the query emptied appends at its true end; a multi-card drop enters in proposal order beside the same anchor. Hidden cards keep their ranks untouched, never displaced by a gesture that could not see them — and the anchor is what makes the gesture's meaning survive the query's clearing: when the filter lifts, the card sits exactly beside the card it was dropped against. The rendered space means everything rendered — the new-card placeholder occupies its slot for the zones exactly as it does for layout. 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.
|
||||
- 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. **A drop under an active filter counts in the rendered space** (ruled 2026-08-06, closing the divergence found 2026-08-01): the zones, the shadow, and the write all resolve through the same filtered list — the position the shadow shows is the landing the write performs (the shipped interim — zones and write counting the unfiltered lane while the slots rendered filtered — was self-consistent but named a position the user could not see; retired). The slot's meaning in the full order is its **visible anchor**: the dropped card enters immediately *after* the slot's visible predecessor — or immediately *before* its visible successor when the slot has none (top of the lane) — and a lane the query emptied appends at its true end; a multi-card drop enters in proposal order beside the same anchor. Hidden cards keep their ranks untouched, never displaced by a gesture that could not see them — and the anchor is what makes the gesture's meaning survive the query's clearing: when the filter lifts, the card sits exactly beside the card it was dropped against. The rendered space means everything rendered — the new-card placeholder occupies its slot for the zones exactly as it does for layout. 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, 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
|
||||
|
||||
@@ -10,7 +10,7 @@ The stance is committed in 00-vision.md: **accessibility is a requirement of "na
|
||||
|
||||
## The board through VoiceOver
|
||||
|
||||
- **Tree shape**: window → lanes (accessibility containers, in lane `order`) → cards (leaf elements, in card `order`). A lane container is labeled "⟨title⟩, lane, N cards" — the count reads the search filter like the visible badge (04-interactions.md). The lane header's new-card button is a labeled child ("New card in ⟨lane⟩"). A card is **one flattened element**: label = title (or the untitled placeholder), value carries the attachment count when present, selected state via trait. Face icon and chips are decorative — folded into the element, never separately focusable: the flattened element carries the attachment count in its value, and the accessible attachment surface is the card window's keyboard-native section (below); the face itself has no media presentation (03-board-ui.md's no-carousel resettlement).
|
||||
- **Tree shape**: window → lanes (accessibility containers, in lane `order`) → cards (leaf elements, in card `order`). A lane container is labeled "⟨title⟩, lane, N cards" — the count reads the search filter like the visible badge (04-interactions.md). The lane header's collapse chevron is a labeled child ("Collapse ⟨lane⟩"). A card is **one flattened element**: label = title (or the untitled placeholder), value carries the attachment count when present, selected state via trait. Face icon and chips are decorative — folded into the element, never separately focusable: the flattened element carries the attachment count in its value, and the accessible attachment surface is the card window's keyboard-native section (below); the face itself has no media presentation (03-board-ui.md's no-carousel resettlement).
|
||||
- **Logical order, not masonry position** (decided): within a wide lane, VoiceOver reads cards by `order` — the interior grid columns are presentation only. This deliberately diverges from on-screen geometry (narrowed by the 2026-07-31 column-major masonry: walking down one column now *is* consecutive `order`; the divergence that remains is a geometry-sorted reading-order sweep — left-to-right, then down — which interleaves the columns); the spatial arrow-key model (04-interactions.md) remains available alongside, since board keyboard navigation keeps working with VoiceOver running.
|
||||
- **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, 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.
|
||||
|
||||
@@ -90,7 +90,7 @@ All board grammar keys are inert while a title editor is focused, and menu dispa
|
||||
- **Lane edge drag** (03 ▸ Lane): window-growing resize between integer widths — the one width control that moves the window.
|
||||
- **Finder file drops** (04): onto a card = attach; onto lane empty space = one card per file; anywhere on the card window = attach (05's payload-split precedence).
|
||||
- **Preview** (05): task-list checkbox toggle (the one interactive exception), link opens, text selection. **In-content controls aren't P-only** (settled): checkboxes and links are real controls in the focus/accessibility tree, so Full Keyboard Access + Space and VO-Space reach them (05 ▸ Task-list checkboxes) — content rides the system focus model rather than earning command rows; "a command absent here doesn't exist" scopes to commands, not content.
|
||||
- **Lane header new-card button** (03, labeled "New card in ⟨lane⟩" — 10): creates in **the button's lane**, appended at the bottom — the click names its target, overriding 04's selection-derived ⌘N target rule (settled); placeholder and abandon semantics exactly as ⌘N. The card window's attachments-section **quiet add affordance** (05) is the same class: a pointer twin of File ▸ Add Attachment…, no separate behavior.
|
||||
- **The card window's attachments-section quiet add affordance** (05): a pointer twin of File ▸ Add Attachment…, no separate behavior. (The lane header once carried a matching "+" new-card button with its own click-names-target carve-out over 04's ⌘N rule; it is removed — every creation path now goes through the selection-derived rule, Return on a selected lane, or empty-space double-click.)
|
||||
- **Attachment rows** drag out their file URL (05). **Welcome rows**: single click selects, double click opens (03).
|
||||
|
||||
## Context menus
|
||||
|
||||
Reference in New Issue
Block a user