One named home for every curve and duration — the two-voice split 03
fixes (snappy structural: drag reflow 0.18, delete 0.25, lane resize
0.2; smooth content-reflow 0.28), the scale-and-fade appear/disappear
transitions (cards 0.8, lanes 0.9), and a Reduce Motion variant on
every accessor (animations go instant, transitions go crossfade). A
post-migration grep holds the invariant: zero motion literals outside
Motion.swift. The animate-vs-snap split lands where Lanework's
one-way flow puts it: the store's reload seam. An app-mediated echo
applies its snapshot inside the structural transaction; foreign and
reconciling reloads — and every bracket-ending wholesale reload —
assign bare, because live-reload is the board becoming what's on
disk, not an event to perform. A window that merged foreign events
into an app-mediated span animates, deliberately: the ratified merge
rule makes it indistinguishable from a pure echo, and a test pins
that reading so a future mixed case fails loudly. The lane-reorder
reflow keys on the drop proposal alone (pointer tracking stays 1:1),
the resize session freezes its Reduce Motion answer at drag start so
the unit tick and the window resize can never disagree, and the m5
drag / m5 search / m7 undo voices are named seams waiting for their
call sites. 13 new tests.
Claude-Session: https://claude.ai/code/session_01SR4XGjmBE16ZUYWpfFHXwY
Deletion becomes a two-stage, Finder-style story. File > Delete and
plain Backspace tombstone the live selection; View > Show Trash (no
chord — Shift-Cmd-T stays with the system's tab bar) reveals the
quasi-lane: trailing, one fixed width unit consumed only while shown,
hatched dimmed header, count badge, no new-card button, exempt from
resize and reorder alike. Its contents are a pure view over the
snapshot — the deterministic sort (deleted newest first, folder-name
ties, unparseable stamps oldest) interleaves card rows with a
tombstoned lane's single entry, whose count names what Put Back
returns; the ancestor walk is absolute, so an own-flag card beneath a
tombstoned lane has no row and recovery is deliberately two steps.
Put Back twins Delete on Cmd-Backspace with validation enabling
exactly one; restore fidelity is byte-perfect because nothing ever
moved. Delete Immediately confirms exactly where loss is real (every
board is mode-none today; the predicate names the git carve-out for
m7), Empty Trash always confirms with the true whole-board count,
and dragging a tombstoned card onto a live lane restores it there —
positional drops and cross-board locality arrive with m5's machinery.
The banner's delete phrasing drops "move to the trash" per the naming
constraint: board deletion says Delete, "Move to Trash" stays
reserved for the system Trash. 47 new tests.
Claude-Session: https://claude.ai/code/session_01SR4XGjmBE16ZUYWpfFHXwY
The window-title widget arrives as a leading titlebar accessory — a
quiet chevron on board windows only, installed and removed by the
window controller's own attach lifecycle — anchoring the one
board-level surface as a transient popover (the board window
deliberately grows no toolbar item for it). Inside: board rename
editing frontmatter title only (the folder is never renamed; an empty
commit removes the key and the window title falls back to the folder
name), the embedded shared style editor permanently targeting the
board, and the labeled Git section that this milestone only reserves
— a mode-none explanation and a disabled stub where m7's add-git,
branch, remote, and authentication controls land. Cmd-I (File >
Board Info) toggles it per window through a focused scene value,
kept apart from board-scoped transient state since a titlebar
popover belongs to one window, not to the board. Escape reverts a
dirty rename field and falls through to dismiss otherwise; a foreign
rename resyncs the field only while unfocused. 12 new tests.
Claude-Session: https://claude.ai/code/session_01SR4XGjmBE16ZUYWpfFHXwY
One style-editor component, anchor-agnostic: a background grid (None
well plus the 12 palette colors) and a curated symbol grid (the
pathfinder's five-dozen set, leading well removing the icon key for
the level default), selection-aware across cards, lanes, and the
board itself. Batch edits compute per-dimension state — uniform,
mixed (no well selected), or an off-palette value labeled verbatim
outside the grids — and choosing a well applies to the whole target
set as one write bracket, skipping no-ops per field. The popover
tracks its target set live per the freshly ratified rule: targets
re-resolve by UUID on every reload, a vanished target leaves the set,
an emptied set dismisses the editor, and nothing ever silently
retargets to the board. Anchors landing now: Board > Style
(Opt-Cmd-S) and the card/lane context menus, which also carry the
quick-style recents row (app-wide, persisted, capped at six, None
never recorded) and the lane's width control twinning the menu
chords. The styling system's other two renders arrive with it: a
lane's background paints the C7 top-edge band, the board's paints
the window content background — malformed values paint nothing and
stay byte-identical on disk. 31 new tests.
Claude-Session: https://claude.ai/code/session_01SR4XGjmBE16ZUYWpfFHXwY
The lane title bar becomes real: leading SF Symbol (hand-written names
render leniently, unknown ones fall back to the level default), title
or secondary untitled placeholder, a quiet count badge that counts
exactly the cards the body renders (so the m5 search filter is
followed by construction), and a new-card button. The whole bar is
the reorder drag surface — no grip — with click-vs-movement splitting
select from drag; a pure proposal function maps the drag to an
insertion index and release commits through the Writer's same-parent
degenerate reorder, compacting and retrying when midpoint precision
runs out. Clicking never edits: inline rename is Return on the sole
selected card or Board > Rename for either kind, a third transient
editor beside the placeholder that tracks its target by UUID, commits
on focus loss, discards silently when the target vanishes, and
removes the title key on an empty commit. The new-card placeholder
renders at last — the settled Cmd-N target rule (pure, tested) files
it after the anchor card, at a selected lane's bottom, or into the
last-active lane; Return commits and re-selects the lane, Cmd-Return
also opens the card window, and a failed create discards the overlay.
New Card / New Lane / Rename land in the menus with focused-editor
and read-only validation; rename gets its own WriteOperation case in
the banner vocabulary. 59 new tests.
Claude-Session: https://claude.ai/code/session_01SR4XGjmBE16ZUYWpfFHXwY
The lane strip replaces the placeholder board: window width divides
across the lanes' width units (no horizontal scroll, no minimum width,
degenerate compression accepted), a lane of n units flowing its cards
into n round-robin masonry columns via a measurement-cached Layout.
Width has two deliberately opposite controls, both landing here: the
right-edge drag (ported verbatim from the pathfinder's ColumnResize)
freezes the 1x standard at drag start, snaps between integer widths
with the asymmetric shadow-leads tick and 10pt re-entry, grows the
window one standard width per snap so siblings keep their exact
pixels, and rubber-bands at the screen's visible frame — uncapped
otherwise; the Increase/Decrease Lane Width items (new Board menu,
Cmd-Opt-arrows) are the stepper's keyboard face and re-divide the
existing window width instead, never touching the window. Width
changes write through the new .resize WriteOperation ("Couldn't
resize…" in the banner vocabulary, which grows with the surfaces by
design); malformed width values render as one unit and stay untouched
on disk. 27 new tests port the pathfinder's resize-math suite onto
the uncapped range and pin the write path's fidelity.
Claude-Session: https://claude.ai/code/session_01SR4XGjmBE16ZUYWpfFHXwY
TransientBoardState, one per store, holds state by how a reload treats
it: item-referencing sets (selection, drag membership, pending cut)
share one shape and one constraint rule — members must exist in the
current universe — applied in two directions by one primitive, so the
search filter's hidden-cards rule and reload survival are one rule
expressed once; derived state is stored as its inputs only (the query,
never its result set); and the new-card placeholder is a lane-anchored
overlay with no UUID until commit, discarded when its lane vanishes or
tombstones, handed off when the created card's UUID appears. Trash
visibility rides along per-open, never persisted. The decision is
written back into DESIGN/02 § Changes from Kanban — the TBD is closed.
Full suite 352 tests in 64 suites green. Two findings filed.
Claude-Session: https://claude.ai/code/session_018BjQRYBR6jQja3jCRi5S3A
The banner surface as one vocabulary (Kanban/LiveStore/BannerCenter,
Kanban/UI/BannerStripView): a pure precedence rule — in-progress pinned
above the collapse (ratified mid-build), lock > breakage > one-shot
write failures > commit+attachment, signposts last — with all
user-facing phrasing owned here via exhaustive switches over the closed
WriteOperation enum; free-form English survives only in diagnostics.
performWrite posts its failures before rethrowing, so no one-shot can
bypass the strip; refusals under lock post nothing.
The lock vocabulary completes: vanishedRoot and unwritableLocation join
bracketedReloadFailed, each with its own clearing rule (unwritable
clears only on a reconciling reload's writability re-probe). The
registry now owns root recovery: bookmark re-resolution absorbs renames
transparently, a dead root locks read-only and re-arms FSEvents on the
gone path so the root's return round-trips back through rootChanged,
re-minting and re-keying on the way. DirtyBufferGuard is the one modal
moment, retry / save a copy / discard, no fourth button.
36 new tests; full suite 333 tests in 62 suites green. Five findings
filed on the Redesign board.
Claude-Session: https://claude.ai/code/session_018BjQRYBR6jQja3jCRi5S3A
The parallel design session settled the tombstoned-ancestor question:
a card counts as trashed if its own flag or its lane's says so, so
tombstoning a lane ejects its cards from a live selection — they render
nowhere once the lane collapses to a single trash entry, and nothing
invisible may stay selected, drag-included, or pending-cut. One-line
predicate change in Selection.resolved(against:), plus the test.
Full BoardStore suite green (15 tests).
Claude-Session: https://claude.ai/code/session_018BjQRYBR6jQja3jCRi5S3A
Per-board @Observable MainActor hub (Kanban/LiveStore/): watcher signals
drive off-main tree walks with a generation guard, single-flight
coalescing (strongest pending origin, watcher's merge rule), and the
resilience contract — a failed reload never replaces a good snapshot,
per-file breakage never locks editing, and a wholesale operation
(performWholesale) arms a reload-must-succeed-or-lock floor so a failed
post-bracket reload flips the board read-only until a good reload heals
it. Selection is a pure UUID-set value re-resolved on every swap;
liveness flips eject. performWrite brackets the watcher so Writer
round-trips come back app-mediated.
14 store tests; full suite 279 tests in 54 suites green. Three findings
filed on the Redesign board.
Claude-Session: https://claude.ai/code/session_018BjQRYBR6jQja3jCRi5S3A