Commit Graph
3 Commits
Author SHA1 Message Date
rzen c339b4cecf Implement live accessibility announcements
The board speaks when files change under the user, per DESIGN/10 § Live
board announcements. BoardDiff is the pure snapshot summarizer (identity
sets for cards/lanes added/edited/moved/deleted — ids, not tallies, so
pro-m1's semantic commit engine can build on it; edited = rendered
content only, moved beats edited, implied events don't steal the
subject). BoardAnnouncer is the decision seam: focusOutcome computes the
vanishing-focus sentence and the walk-up-then-sideways recovery (next
lane by order, else previous, board container only when none remain,
never the trash); speech(for:) is the one-sentence precedence ladder —
raised condition > bracket completion > cleared condition > vanished
focus > digest — foreign-only for the last two rungs, so app-mediated
echoes stay silent.

BoardStore.land assembles ReloadFacts and posts exactly one sentence per
reload through the injectable announce outlet (AccessibilityAnnouncer,
medium priority, never interrupting). Selection recovery layers on top
of ItemReferenceSet re-resolution — survivors veto, the emptied
selection lands on the vanished item's lane and re-arms ⌘N's active-lane
memory. performWholesale(announcing:) arms a completion phrase consumed
by the closing reload — nil on every base bracket today; pro-m1 fills
git phrasings. Locks raised outside the reload path (vanished root,
unwritable location) announce through the same ladder, and the banner
strip is a labeled "Board status" container whose row labels are the
announced sentences (AccessibilityPhrases.bannerLabel — one string for
eye and ear).

Announcements classify at reload granularity (WatchOrigin) as a
deliberate interim: DESIGN/02's EchoLedger (per-file classification, the
announcer's specified input, git-free) was scheduled with the
auto-committer that the edition split moved to pro-m1 — filed on the
Redesign board for a ruling. 1533 unit tests green, both schemes build.

Claude-Session: https://claude.ai/code/session_01SR4XGjmBE16ZUYWpfFHXwY
2026-07-29 08:15:53 -04:00
rzen 53bc71f7fb Materialize the trash — store, undo, and the container universe
Phase 2 swaps every consumer: Liveness and its ancestor walk are gone,
replaced by ItemContainer — a UUID set plus the container side it
lives on, presence the whole test, one selection boundary instead of
the old liveness law. Deletion stages by place: board cards move to
the trash at a store-minted head rank, trash-side delete is permanent
behind its confirmation, Delete Immediately skips the trash from
anywhere, lane delete captures the subtree and removes the folder.
Restore has no method at all — moveCards resolves members in either
container, so drag-out and cut-paste are the ordinary moves 13 calls
them, registering ordinary Move steps. The delete inverse moves the
card back to its captured lane and rank; redo replays the captured
trash rank, a value the gesture actually wrote; lane undo recreates
the subtree byte-faithfully in session. Purges register nothing —
where 13's trash section contradicts its own Rules on that, Rules
wins, filed for ruling. Staleness collapsed to present-or-absent: a
container is a path, so a foreign restore fails the delete step's
expectation structurally. Legacy tombstones migrate on the loose-file
tail hook, cards oldest-first so minting above top reproduces the
retired newest-first column, lanes returning live, one folded loss
row naming both directions. Put Back, restoreByDrag,
receiveRestoredCards, TrashEntry, and the kind machinery are deleted;
the trash column renders the container correctly with its full face
rework left to phase 3.

Claude-Session: https://claude.ai/code/session_01SR4XGjmBE16ZUYWpfFHXwY
2026-07-28 17:47:56 -04:00
rzen 2e229735b1 Implement the selection model
The full pointer grammar of 04-interactions.md § Selection, stated once
as a pure function (SelectionGrammar) and reached through one store
funnel from every click surface — card face, lane header, lane empty
space, trash row:

- Plain click replaces and anchors; the lane surfaces (header and empty
  space alike, per the settled one-lane-click-behavior rule) toggle off
  on a sole-membership repeat.
- ⌘-click toggles within a homogeneous set; crossing any axis — cards
  XOR lanes, live XOR trashed, card entries XOR lane entries in the
  trash — degrades to a replace, so no click can produce a mixed
  selection.
- ⇧-click ranges from the anchor in the (side, kind) order list: flatten
  order for cards, lane order for lanes, the trash's deterministic sort
  filtered to kind — the pointer twin of the keyboard's boundary rule
  (the keyboard goes inert, the pointer skips).
- The rubber band (MarqueeSession/MarqueeMath) arms from lane empty
  space, the board backdrop, and the trash column; side frozen at the
  origin, trash bands homogeneous by topmost kind, frames self-registered
  in strip space, geometric begin guard, never animated.
- Fast plain double-click opens the card window (⌘↩'s pointer twin);
  Select All answers the standard Edit menu item via the responder
  chain, trash- and kind-respecting.
- The range anchor lives in TransientBoardState beside the selection and
  obeys the same reload vanish rule.

659 unit tests (28 new in SelectionGrammarTests).

Claude-Session: https://claude.ai/code/session_01SR4XGjmBE16ZUYWpfFHXwY
2026-07-27 19:00:23 -04:00