BoardRecord carries icon/iconColor; recordOpen/recordClose stamp them with the
display name, and a displayStateDelegate on the store (wired in BoardWindowHost
beside onFrameChanged) syncs all three through BoardRegistry.syncDisplayState on
every successful reload — welcome rows now wear the board's own icon and follow
in-app renames live. recordOpen now runs before the load with the folder name as
a brand-new record's provisional display name, so a first open that fails
fail-fast still lands in recents carrying the failure row-level (02's rule); an
existing record's cached name survives a failing retry, and the welcome fallback
list remains only for failures naming no record at all.
Claude-Session: https://claude.ai/code/session_01CqjXB7ASoWtbyoGod68k97
Edition split follow-through: 12-editions.md and 13-native-undo.md join the
corpus; git-era docs (06, 07) are Pro-scoped and cross-references realigned.
Resolution session rulings written in place: caret chords yield to any focused
text control (04, 11); the drop settle holds its proposal as overlay state and
02 gains the overlays family; Duplicate gets the save-panel fallback and a
cancellable copy walk (03); Finder open is a standard document open (02);
failed first opens record before loading with the folder name provisional (02);
trash pointer ranges skip by kind, full-height marquee surface, Select All by
kind, rows-only trashed universe (02, 04); Empty Trash counts entries (03);
create handoff reads as one arrival (02); range-anchor lifecycle, board-
background click grammar, jump landing cards, lane-domain shift-arrows (04);
Reduce Motion restated per voice (10).
Claude-Session: https://claude.ai/code/session_01CqjXB7ASoWtbyoGod68k97
DESIGN edits landing from the parallel session, honored:
- Caret chords yield to any focused text control (04 ▸ Grammar): Move
Left/Right ⌘←/⌘→ and the width pair ⌥⌘←/⌥⌘→ now disable while the board
popover is open — its fields are the one non-inline text surface a board
window has today; the search field and card-window fields extend the rule
with their own cards.
- The width pair batches over a multi-lane selection (03 ▸ Lane, the styling
precedent): each selected lane steps one unit through one bracket
(stepLaneWidths); floor members hold on a decrease; the context-menu
stepper stays single-lane by nature.
- A width write landing on 1 removes the width key (03 ▸ Lane, the
remove-at-default family), every mechanism alike — writeLaneWidths is the
one commit point; a hand-written width: 1 is preserved by the unchanged
guard until the app itself next edits width.
Claude-Session: https://claude.ai/code/session_01SR4XGjmBE16ZUYWpfFHXwY
The pathfinder's drag-reorder model, ported and generalized (DRAG-REORDER.md
travels with it, rewritten for lanes, the interior masonry, multi-drag,
cross-board sessions, the re-grounding trio, and the committed-overlay hold):
- DropSlotMath — resting-layout zones from analytic lane arithmetic and the
pure masonry placement (MasonryLayout now lays out through the same
MasonryPlacement the drag reads, so geometry cannot drift), span-capped
triggers sized to the dragged run's future footprint, hysteresis holds with
the fresh-entry fallback, boundary ties, own-slot no-ops; nil means hold.
- DragAutoScrollMath — the activation bands and velocity ramp, pure.
- The drop commits, one performWrite bracket each: moveCards/copyCards within
a board (insertion ranks touch only the dragged cards; renumber fallback);
receiveCards/receiveLanes/receiveRestoredCards on the destination store for
cross-board copy and ⌘-move with the import-boundary remint, lane copies
stripping tombstoned cards while moves carry them; restoreByDrag is now
positional, writing order only when the drop names a new one.
Gestures, sessions, previews, and delegates are the second half.
773 unit tests (87 new since the keyboard grammar).
Claude-Session: https://claude.ai/code/session_01SR4XGjmBE16ZUYWpfFHXwY
The board's fixed grammar keys and the menu-backed chords of
04-interactions.md § Keyboard, per the Command Nexus inventory:
- Spatial arrow navigation (NavigationMath.nearest over the marquee
registry's frames — one geometry source), walking across interior
masonry columns, lanes, and into the shown trash; ⇧-arrows extend via
the same range function as ⇧-click and go inert at the liveness and
kind boundaries; ⌥-jumps with the ⌥↑ lane-domain escalation and ↓
descent; the empty selection seeds at the first lane's first card;
selection scrolls into view.
- selectionHead — the navigation cursor beside the anchor, set by every
click, moved by every arrow, dropped by the reload vanish rule.
- Board ▸ Open Card ⌘↩ (the one command enabled mid-edit: commits the
placeholder or rename and opens), Move Up/Move Down ⌥⌘↑/⌥⌘↓
(within-lane sort, gather-then-step, rank-permuting writes in one
bracket), Move Left/Move Right ⌘←/⌘→ (sole lane, one slot, never the
trash) — all validating and acting off one shared answer.
- Delete now selects the Finder-style successor sibling from the
pre-write snapshot, so repeated ⌫ walks down a lane; external
vanishing still only shrinks the selection.
- handleReturn rejects modified Returns; the trash column renders
eagerly so every row stays registered for navigation and the marquee.
686 unit tests (27 new).
Claude-Session: https://claude.ai/code/session_01SR4XGjmBE16ZUYWpfFHXwY
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
The welcome window becomes the real thing: Xcode-style, hidden title
bar with background drag, branding and actions left, recents right —
rows carrying the board symbol, name, location, and the registry's
cached lane/card counts (stamped at close, never a scan at welcome
time), sorted by last opened. Launch failures surface row-level per
02: a failure joins its recents row as a warning caption, an
unresolvable bookmark renders unavailable with Forget its one
affordance, and only a failure with no row to carry it falls back to
a compact list; a board opening again heals its row. New Board
(Opt-Cmd-N) opens the Pages-style template chooser — shipped with
the single Basic template and the m9 seams marked — flowing through
the save panel into createBoard/createLane and straight into a board
window. Open Recent gains its submenu with Clear Menu (byte-identical
to forgetting every row, pinned by test), and File > Duplicate forks
the frontmost board to a Finder-style copy sibling: pending work
flushes first through the close flush's step two alone (sessions stay
open — 09's stated exception), every GUID and tombstone carries (the
whole-board carve-out from copies-remint), and the copy opens in its
own window while the original stays put. 36 new tests.
Claude-Session: https://claude.ai/code/session_01SR4XGjmBE16ZUYWpfFHXwY
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
The corpus just ratified the plural case: a multi-selection anchors
at its last member in flatten order (lane order, then card order —
the multi-drag order, the same anchor paste will use), so creation
follows the last selected card or appends to the last selected lane
instead of falling through to the last-active lane. One display-order
walk keeps the resolver pure; the sole selection is now just the
degenerate one-member case.
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 design corpus ratified that ranges are part of a sensible reading:
an exact-integer width below 1 now coerces to 1 read-side (bytes
untouched) instead of reading as malformed — the width division must
never see a zero or negative unit — while a non-finite order (.nan,
.inf) is now the same loud malformed-order rejection as a non-numeric
one, guarded at the single point where the double arrives so loader
and Writer inherit it together. The symlink-never-traversed rule
turned out to be already enforced (the loader has filtered symlinks
ahead of the directory check since the first commit); it and the
copy-preserves-the-link-verbatim behavior are now pinned by tests,
alongside the two hostile shapes the corpus names (width: 0,
order: .nan). Five new tests.
Claude-Session: https://claude.ai/code/session_01SR4XGjmBE16ZUYWpfFHXwY
The card face becomes real: leading SF Symbol (card default doc.text,
tinted by a valid hand-written iconColor — schema yes, control no),
title or the quiet untitled placeholder, and a quiet paperclip when
the card has attachments — title-only by design, no body excerpt.
Color is the settled K1 edge accent, not a fill: background paints a
4pt stripe down the left edge, resolved through the ported pathfinder
palette (12 icon tints + 12 backgrounds carried over verbatim, plus
raw #RRGGBB[AA]); anything unresolvable paints nothing and stays on
disk exactly as written. The snapshot now carries each card's flat
attachment names — the loader's one read inside a card folder, shared
with the Writer's listing so the m5 carousel and m6 sidebar can never
disagree on order (Finder order, the Writer's existing comparator).
The face keeps its top-aligned structure so the sole-selection
carousel can expand inside the card without moving masonry neighbors.
18 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
Four scenes (welcome, restore bootstrap, board group, card group) with
system restoration disabled in favor of the registry's open-now flags:
set when a window actually opens, cleared only on user close, so quit —
and crash — leave exactly the restoration set behind. AppModel joins
windows to sessions (shared store, registry record, card refs, held
security scope); CloseFlushCoordinator pins 02's strict close order as
a seam-injected machine (card sessions end, windows drain, store
flushes, record stamps, teardown) with named slots where m6/m7 flushes
land. HostedWindowController proxies — never replaces — SwiftUI's
window delegate to intercept windowShouldClose for the flush, report
frames, and place saved frames onto live screens. Card windows are
(board path, case-folded card id) values: reopen focuses, and a
snapshot-pure fate function dismisses on delete, tombstone, tombstoned
lane, or cross-board move.
Claude-Session: https://claude.ai/code/session_01SR4XGjmBE16ZUYWpfFHXwY
- 02: failed create discards the placeholder; a second creation is a
click-away for the open draft (replace-with-discard, ⌘N unreachable);
unwritable-lock banner names volume vs permissions; WriteOperation
vocabulary grows with rename/body-save surfaces; one bookmark per open
board threads through the open flow; in-progress banner class ratified
newest-first; signpost lifecycle is the dismissable untimed one-shot
- 01: folder-name tie-breaks compare the canonical lowercase spelling
everywhere (Ranks and trash sort)
Claude-Session: https://claude.ai/code/session_01HJ7PhFNmQ19bvy9RMD6GSb
Two follow-ups to this session's decisions: the reconciling reload's
writability re-probe now raises the unwritable lock as well as clearing it
(fail loudly once, not per gesture), and under the shape-only identity
predicate the loader deterministically dedupes case-spelled sibling twins
on case-sensitive volumes (lowercase spelling wins, else lexicographic
first; loser takes the stray posture) so a snapshot never carries
duplicate ids.
Claude-Session: https://claude.ai/code/session_01HJ7PhFNmQ19bvy9RMD6GSb
Researched against the Frontmatter implementation: the duplicate-key
rescue's retry-loop pattern (compose a placeholder-patched copy, derive
spans from original lines) extends directly. Ratified: a top-level key
line whose plain unquoted remainder trips "mapping values are not allowed"
reads as everything after the first ": "; quoted/flow remainders and
nested shapes stay fail-fast; bytes preserved, fully editable, heals on
first app write of the key; silent with a log line. Test obligation named
(no hostile-YAML case covers the shape today).
Claude-Session: https://claude.ai/code/session_01HJ7PhFNmQ19bvy9RMD6GSb
- 02: welcome counts are live items only; failed first opens still record;
duplicate-identity registry records merge silently (newest wins);
BoardStore's FolderWatcher pointer reads "below"
- 11: lane-header new-card button inventoried (click names its target lane,
overriding 04's ⌘N rule — cross-referenced there); attachments quiet-add
affordance listed as a twin; Undo/Redo and Pull/Push rows gain the
abnormal-state pause, read-only lock, and unresolvable-remote conditions;
welcome scope added to Reveal in Finder
- 03/11: one lane context menu (header or empty space) with the full
inventory, replacing the split rows
- 09: instantiated boards are never in git mode — actual mode follows
detection at the destination (repo-nested possible)
- 08: the app never writes an existing CLAUDE.user.md; one-time rescue
creation stated as the exception
- README: pathfinder path corrected to ../../Kanban
Claude-Session: https://claude.ai/code/session_01HJ7PhFNmQ19bvy9RMD6GSb
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 composer was a pure snapshot diff while the repo tracked files outside
the model (agent guide, strays, .gitignore), leaving stray-only windows
uncommittable and the guide's own subject underivable. Settled: the
committer stages the whole root with the tree as its commit condition; the
composer also receives the changed-path list, composing "Update agent
guide (vN)" (version from the marker line) and "Update '<path>'" events
that ride as body bullets under model-event subjects; attribution falls
out of the path-keyed EchoLedger.
Claude-Session: https://claude.ai/code/session_01HJ7PhFNmQ19bvy9RMD6GSb
The detached-HEAD/mid-merge pause named auto-commit, Undo/Redo, and branch
controls but left Pull, Push, and push-on-commit live — against a dirty
tree whose clean-by-pull-time invariant the held auto-commit had falsified.
Settled: the whole surface pauses; the ahead/behind badge keeps counting
since a fetch is a read.
Claude-Session: https://claude.ai/code/session_01HJ7PhFNmQ19bvy9RMD6GSb
Show Trash's ⇧⌘T collided with the Show Tab Bar item macOS injects into
multi-window apps. Settled: tabbing is kept as a system affordance (each
tab a full board window, per-board frames applying when standalone) and
Show Trash ships with no default chord, assignable via the remap path.
Claude-Session: https://claude.ai/code/session_01HJ7PhFNmQ19bvy9RMD6GSb
The field's exits were commit-only, silently diverging from the board
rename's Escape-abandons rule, and Escape was ambiguous with the body in
Edit mode. Settled: Escape reverts to the on-disk title and focuses the
body; the collision resolves by focus per 06's first-responder routing.
Claude-Session: https://claude.ai/code/session_01HJ7PhFNmQ19bvy9RMD6GSb
The trash trio had no verbs — tombstoning and purging both composed
"Delete card 'X'", erasing the moved-to-trash vs gone-forever distinction
the docs ask users to learn. Settled by diff shape alone: deleted:
appearing = Delete, clearing = Restore, item leaving the tree =
Permanently delete; plural folding keeps Empty Trash distinct from a
multi-select delete.
Claude-Session: https://claude.ai/code/session_01HJ7PhFNmQ19bvy9RMD6GSb
The rubber-band had its boundary sentence; the keyboard extend gesture had
none. Settled: a ⇧-arrow that would cross live↔trash or card↔lane-entry is
inert — never move-and-replace — keeping the homogeneity invariants
unbreakable by keyboard while plain arrows still walk across.
Claude-Session: https://claude.ai/code/session_01HJ7PhFNmQ19bvy9RMD6GSb
Paste's anchor rules never accounted for a tombstoned selection, and a
literal reading filed a pasted lane beside a trashed card's on-disk lane.
Settled: ⌘V stays enabled and behaves as with nothing selected (card
payload to the last-active lane, lane payload to the board's right end).
Claude-Session: https://claude.ai/code/session_01HJ7PhFNmQ19bvy9RMD6GSb
The fixed grammar keys were formally live over the board while the search
field had focus, letting a reflexive Return fire rename/create and destroy
the query. Settled: every key acts on the field (no pass-throughs), Return
no-ops, Tab moves focus to the filtered board with the query intact, and
Escape stages — clear query, then exit field, with board-focus Escape
clearing an active search before its clear-selection meaning.
Claude-Session: https://claude.ai/code/session_01HJ7PhFNmQ19bvy9RMD6GSb
Accept liberally, emit conservatively (764a4d4): the gate is 8-4-4-4-12
hex in any case and any UUID version — uuidgen and UUID().uuidString
print uppercase, and a strict lowercase gate would silently stray an
agent's standard-tool card. Identity comparison is UUID-value equality
everywhere: ItemID keeps its byte-faithful rawValue but equates and
hashes on the lowercased canonical form, and the writer's
import-boundary collision check canonicalizes, so a same-UUID arrival
spelled in another case remints instead of slipping past. The app still
mints only lowercase v4 and never renames to canonicalize.
Full suite 342 tests in 63 suites green. Two findings filed.
Claude-Session: https://claude.ai/code/session_018BjQRYBR6jQja3jCRi5S3A
Every lane command keyed off "a selected lane" with no drag-free way to
create one. Settled: ⌥↑ with the lane's first card selected selects the
lane (↓/⌥↓ descend back; ⌥↑ then inert), an empty selection seeds at the
first lane's first card, and a plain click on the lane title bar selects
with the drag surface engaging only on movement.
Claude-Session: https://claude.ai/code/session_01HJ7PhFNmQ19bvy9RMD6GSb
The row's name and icon implicitly required reading each recent board's
root index.md at welcome time — the hang the counts rule was written to
avoid. Settled: title/icon/iconColor live in the registry record; they
refresh whenever an open board's reload changes them (in-app renames land
instantly, foreign renames of open boards ride along), with closed-board
renames stale until next open like the counts.
Claude-Session: https://claude.ai/code/session_01HJ7PhFNmQ19bvy9RMD6GSb
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
Both whole-board copies were absent from the lock's command enumeration,
staying enabled while the source tree was gone, unknown, or unwritable and
while the lock suppressed their required close flush. Settled: Duplicate
disables in every lock state; Save as Template disables except under the
unwritable-location lock, where reading the board into Application Support
is a legitimate copy-out with a vacuous flush precondition.
Claude-Session: https://claude.ai/code/session_01HJ7PhFNmQ19bvy9RMD6GSb
The stacking order covered only error classes, letting an in-progress row
collapse behind "+N more" — the sole explanation of a bracket's write lock
and a deferred close. Settled: in-progress rows pin above everything,
exempt from and uncounted by the collapse; the passive remote-change
signpost ranks below all error classes and may collapse.
Claude-Session: https://claude.ai/code/session_01HJ7PhFNmQ19bvy9RMD6GSb
The trash's newest-first order is load-bearing for keyboard input, but
same-second multi-deletes and 01's blessed-unparseable timestamps had no
ordering. Settled: ties break by folder name ascending; date-unknown
entries sort as oldest, after all dated entries; lane entries interleave
by their own stamp.
Claude-Session: https://claude.ai/code/session_01HJ7PhFNmQ19bvy9RMD6GSb
01 said "lowercase UUIDv4" without defining the test, and the strict
reading made every uppercase uuidgen/UUID().uuidString folder a silently
skipped stray. Settled: 8-4-4-4-12 hex in any case and any version is an
identity; comparison is UUID-value equality (case-spellings are one
identity, matching default-APFS behavior); the app emits lowercase v4
only and never renames to canonicalize.
Claude-Session: https://claude.ai/code/session_01HJ7PhFNmQ19bvy9RMD6GSb
The registry recorded recency but not which boards were open at quit.
Settled: each record carries an open-now flag set on open and cleared on
user-initiated close only — quit teardown leaves it standing, so there is
no at-quit write and crash recovery falls out for free. Restoration
reopens flagged records by lastOpened order; the preference only gates
consulting the set.
Claude-Session: https://claude.ai/code/session_01HJ7PhFNmQ19bvy9RMD6GSb
10 recovered focus "to the card's lane" even in its own lane-tombstone
case, where that lane renders nowhere. Settled: recovery walks up then
sideways — the lane at the vanished lane's position (next by order, else
previous), board container only when no lanes remain, never into the
hidden trash; the announcement names the lane, not the card.
Claude-Session: https://claude.ai/code/session_01HJ7PhFNmQ19bvy9RMD6GSb
10 promised toggleable-without-pointer, 11's normative inventory had the
toggle as pointer-only, and 02's lock disabled it "via menu validation"
with no menu item to validate. Settled: checkboxes and links are real
controls in the focus/accessibility tree — FKA + Space and VO-Space toggle
them; in-content controls are content, not commands, so 04's menu-item
contract and 11's existence rule scope to commands; under the read-only
lock the controls disable in place.
Claude-Session: https://claude.ai/code/session_01HJ7PhFNmQ19bvy9RMD6GSb
03's Put Back cascade ("restores the lane too") was unreachable dead text
under 01's hide-the-subtree consumer rule and 02's effective liveness.
Settled: the lane's single trash entry subsumes everything beneath it; its
count is what Put Back returns to the board; lane Put Back splits by flag
(hidden-with-lane cards return, individually tombstoned ones reappear as
trash rows), making that recovery deliberately two steps.
Claude-Session: https://claude.ai/code/session_01HJ7PhFNmQ19bvy9RMD6GSb
04's drag rule called a within-board lane duplicate unsupported while the
clipboard rules quietly permitted it via same-board lane paste. Settled in
favor of the clipboard: the duplicate is supported there (fresh GUIDs,
tombstones stripped, no menu-validation special case) and merely not
available by drag — ⌥ stays ignored on lane drags so the gesture remains a
clean reorder.
Claude-Session: https://claude.ai/code/session_01HJ7PhFNmQ19bvy9RMD6GSb
01's table promised an update on every app write, which raw-source Apply
(byte-for-byte contract) and attachment operations (never touch index.md;
load-bearing for 03's Add-Attachment-during-raw-source rule) cannot honor.
Settled: modified updates only on app writes that rewrite the item's
index.md; both exceptions stated as deliberate, and the stale-readout
consequence on 05's Modified line accepted with attachment history living
where the files do.
Claude-Session: https://claude.ai/code/session_01HJ7PhFNmQ19bvy9RMD6GSb
06 and 10 consumed a per-file app-vs-foreign classifier that 02's component
inventory never named and whose layering prose seemed to deny. Settled: a
new EchoLedger component records each BoardWriter operation's expected
outcome (content hash / absence / move pair); final-content matching
classifies each observed file, deciding both races (byte-identical foreign
write → app-mediated, accepted; same-window foreign overwrite → foreign,
last writer wins). Ledger is in-memory and per-store; launch catch-up is
all-foreign; bracketed operations bypass it. "Trusts its own writes no more
than anyone else's" is scoped to rendering; BoardWriter's no-hidden-state
bullet stands.
Claude-Session: https://claude.ai/code/session_01HJ7PhFNmQ19bvy9RMD6GSb
WriteOperation replaces free-form operation strings throughout
BoardWriter, per the settled rule in 02 § Write-failure surfacing: the
banner layer will switch exhaustively over it, so a new operation
without a banner rendering is a compile-time hole. Titles enrich at the
two points a document read makes them known (updateIndex, and the
move/copy pre-flight), so failures after the read name the item;
purge never reads and stays title-less. Free-form English survives
only in the diagnostic reason.
Full suite 300 tests in 56 suites green.
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
Two registries (Kanban/LiveStore/): BoardStoreRegistry shares one live
store and one started, fully wired watcher per open board across its
windows — keyed by file identity (fileResourceIdentifier), never path,
refcounted to order teardown; release matches by store identity so a
board renamed while open can't leak its watcher. BoardRegistry persists
app-private per-board state in Application Support as diff-stable JSON:
records anchored by security-scoped bookmarks, recents = the registry
sorted by lastOpened (counts registry-cached, never scanned), graceful
orphaning with Forget, corrupt files quarantined aside, and files-first
verified — the board tree is untouched byte-for-byte. Timestamps use
ISO8601DateFormatter with fractional seconds: the FormatStyle variant
truncates-then-rounds and drifts a millisecond per round trip.
16 registry tests; full suite 297 tests in 56 suites green. Four
findings filed on the Redesign board.
Claude-Session: https://claude.ai/code/session_018BjQRYBR6jQja3jCRi5S3A
Tombstoning a card's lane ejects the card from selection, drag
membership, and the pending cut - liveness is ancestor-walked, since
the card renders nowhere once 03 collapses the lane to a trash entry.
BoardStore's bullet now says it coalesces reloads (one walk in flight)
while the debounce lives in FolderWatcher, matching m3 as built.
Claude-Session: https://claude.ai/code/session_01HJ7PhFNmQ19bvy9RMD6GSb
The next-reload-or-lock expectation arms on every bracket exit, thrown
operations included - a mid-flight failure is when the tree's state is
least known, so the mandatory final reload runs regardless.
Claude-Session: https://claude.ai/code/session_01HJ7PhFNmQ19bvy9RMD6GSb
The parallel design session resolved the m3 watcher findings; two
changed behavior: .git internals are now filtered at any depth (a
nested clone or submodule is a stray whose internals never render),
and a root change landing mid-bracket is owned by the root-change
path — endBracket() skips its mandatory reload once a .rootChanged
tore the stream down, cleared when a fresh stream attaches. The skip
is scoped by an explicit flag, not by stream absence: a watcher whose
stream never came up still delivers post-bracket reloads, since
distrust of FSEvents delivery is the mandatory reload's whole reason.
Two new watcher tests; full suite 281 tests in 54 suites green.
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
Contradictions: v1 gains a minimal Settings pane (⌘,) housing the
restore-at-launch toggle; Window ▸ Welcome to Lanework inventoried in 11.
Gaps: close/quit waits for in-flight bracketed and copy-shaped operations;
pull extends the Edit-session treatment to open raw-source buffers
(signpost + last-writer-wins Apply); File ▸ Duplicate keeps tombstones
(01's "as-is" softened to per-flow exclusions). Minor: Undo routing
promoted to a real heading in 06; 11's dead "10 ▸ Actions" citation
repointed. m3 watcher findings: root-gone cancels armed debounced
delivery; mid-bracket root changes owned by the root-change path;
.git filtered at any depth; banked mid-bracket reconciles and the
200 ms debounce ratified.
Debounced FSEvents watcher over the board tree (Kanban/LiveStore/):
trailing 200ms debounce coalesces bursts; nestable brackets suppress
delivery and close with exactly one app-mediated reload; origins merge
reconciling > appMediated > foreign; missed-events flags degrade to a
reconciling reload; WatchRoot vanish tears the stream down (streams die
and are recreated — reattach() covers rename re-resolution); board-root
.git churn is filtered against the symlink-resolved root. The FSEvents
release callback must be top-level and non-isolated — a MainActor
closure traps in dispatch_assert_queue during stream deallocation.
15 watcher tests (4 consecutive green runs); full suite 265 tests green.
Five design findings filed on the Redesign board.
Claude-Session: https://claude.ai/code/session_018BjQRYBR6jQja3jCRi5S3A
Four minor design-review findings resolved: appended frontmatter lines
adopt the file's prevailing line ending; empty-but-not-blank frontmatter
joins the readable-but-uneditable shapes; an interrupted renumber's
per-file partial is accepted; the write-failure operation becomes a
closed enum carrying the item title.
Claude-Session: https://claude.ai/code/session_01HJ7PhFNmQ19bvy9RMD6GSb
- Appended frontmatter lines adopt the file's prevailing line ending
(read off the opening delimiter) — one stamp never produces a
mixed-ending file.
- Empty-but-not-blank frontmatter ({}, null, ~) joins the
readable-but-uneditable shapes: zero keys load fine, but appending
after that text would be unparseable YAML.
- Renumber is atomic per file, not per lane — an interrupted rescale
is accepted the same way as the shared-board burst: valid floats,
deterministic order, next renumber completes the job.
Claude-Session: https://claude.ai/code/session_018BjQRYBR6jQja3jCRi5S3A
The executable spec for BoardWriter's cross-cutting guarantees, in a
dedicated WriteFidelityTests.swift: minimal-touch verified down to
sibling mtimes across a seven-operation session; renumber pinned as
the sole exception (whole lane rewritten, board root / other lanes /
tombstones untouched); delete→restore byte-precise (only the modified
line differs, zero tombstone residue); unknown-key order preserved
with keys interleaved among schema-owned ones; and a composite
building a board purely through the writer, hand-editing a file like
an agent would, then moving/copying/deleting/renumbering to a
zero-warning reload. Guarantees already pinned by the per-operation
suites (atomicity, move/copy identity, body round-trip) are
referenced, not duplicated. Shared fixtures promoted to
WriterTestSupport.swift. README gains the write-side feature bullet.
5 new tests; 250 total green. Closes milestone m2-storage-write.
Claude-Session: https://claude.ai/code/session_018BjQRYBR6jQja3jCRi5S3A
importAttachments lands files into a card's attachments/ (created on
first import — the one folder the app ever makes there), never
overwriting and never refusing a name: collisions auto-rename
Finder-style, counting up from 2, with a same-named subfolder
blocking a name exactly like a file. Sources are validated before any
copy; a failed copy removes the partial destination and throws naming
the source file; a multi-file batch stops at the first failure with
earlier files staying landed. listAttachments is the flat surface:
top-level regular files only, Finder-sorted, subfolders and hidden
entries excluded and untouched; a missing attachments/ lists empty.
11 new unit tests; 245 total green.
Claude-Session: https://claude.ai/code/session_018BjQRYBR6jQja3jCRi5S3A
deleteItem writes deleted: <now> in place through updateIndex — the
folder never moves, nothing beneath it is touched (hiding the subtree
is the renderer's ancestor walk, not a stored flag). restoreItem
removes the key — position-perfect by construction, every duplicate
occurrence taken so a hand-written twin cannot resurrect the
tombstone. purgeItem physically removes the tree; an already-missing
folder is success (a Finder deletion converges on the same end
state), and no prior tombstone is required (Delete Immediately skips
the stage by design). Board-root deletion is structurally unreachable
via the UUID-shape guard shared with move/copy. Neither delete nor
restore polices liveness — re-deleting refreshes the timestamp,
restoring a live item is a harmless stamped rewrite.
14 new unit tests; 234 total green.
Claude-Session: https://claude.ai/code/session_018BjQRYBR6jQja3jCRi5S3A
moveItem: physical folder move, UUID and created travel unchanged;
exactly one file rewritten (the moved root's order, stamped). Import
boundary detected by resolved board-root comparison; on a cross-board
move-in, arriving UUIDs colliding with any identity in the destination
board (tombstones included) are reminted per folder at the finest
grain — folder rename only, file bytes untouched, every repair
reported in MoveResult. A colliding root moves straight to its minted
name. A same-parent move degrades to a plain reorder, self excluded
from the appended-rank scan.
copyItem: whole-tree copy minting fresh UUIDs at every depth; .fork
keeps created while stamping modified and clearing modified-by, .born
(template instantiation) stamps created fresh too. Nested uneditable
or unreadable files copy byte-verbatim rather than blocking the
gesture; the root must be rewritable. All-or-nothing at the
destination — any failure removes the partial tree.
24 new unit tests; 220 total green.
Claude-Session: https://claude.ai/code/session_018BjQRYBR6jQja3jCRi5S3A
createBoard fills or mints a .kanban (or extension-less) folder and
refuses to clobber an existing board; createLane/createCard mint fresh
lowercase-UUIDv4 folders and append at max+1024 among visible siblings
via the same strict scan renumber uses (extracted, shared). New files
carry schema/title?/order?/created/modified — created==modified, no
modified-by — written LF through the same atomic temp+rename path.
The editability pre-flight is now scoped to operations that rewrite
siblings: renumber refuses on an uneditable sibling, a create beside
one proceeds. Schema constant unified on BoardLoader.supportedSchema.
Info.plist declares the .kanban document type (UTI conforming to
com.apple.package) so Finder treats a board as one document.
20 new unit tests; 196 total green.
Claude-Session: https://claude.ai/code/session_018BjQRYBR6jQja3jCRi5S3A
The single point through which every mutation becomes a filesystem
operation: read fresh from disk (strict byte-faithful UTF-8), refuse
readable-but-uneditable frontmatter shapes, apply the edit through the
span engine, stamp modified / clear modified-by, then temp-file+rename
atomically. Renumber-visible-children is the sole minimal-touch
exception, tombstones untouched. Structured BoardWriteError carries
operation + path + reason for the future banner surface.
Alongside, three edges the card surfaced:
- The loader now decodes byte-faithfully too, so a BOM'd or non-UTF-8
index.md is rejected at load per the encoding contract, instead of
loading via NSString's silent BOM strip and then refusing every write.
- An appended frontmatter line adopts the file's prevailing line ending
(a CRLF file stays uniformly CRLF when a stamp first lands in it).
- Empty-but-not-blank frontmatter ({}, null, ~) is detected as
uneditable — appending after it would be unparseable YAML.
30 new unit tests; 176 total green.
Claude-Session: https://claude.ai/code/session_018BjQRYBR6jQja3jCRi5S3A
- 10 cites 11 for Add Attachment and the lane-width menu items
- 04 uses ⌘/⇧ symbols for click modifiers; card moves scoped to the
⌥⌘ chord; ../WISHLIST.md path fixed
- 03 says stepper (plus the menu pair) in the editing-surfaces table
- 06 says one commit per debounce window
- 03/10 Reduce Motion lists aligned at five items; rubber-band
disambiguated as the lane-resize feedback
- 11 requires the trash shown for Empty Trash
- 02 rewords and-peers
Claude-Session: https://claude.ai/code/session_01HJ7PhFNmQ19bvy9RMD6GSb
Fills the gap: the rename editor follows its card by UUID through
foreign moves, discards silently when the target vanishes or flips
liveness, and a mid-search rename re-runs the predicate like any edit
— creation stays the search filter's only carve-out.
Claude-Session: https://claude.ai/code/session_01HJ7PhFNmQ19bvy9RMD6GSb
Fills the gap: wake/activation reconciling reloads, MustScanSubDirs and
overflow degrade to a full reload, dead streams are recreated at the
re-resolved root. Stale boards structurally excluded.
Claude-Session: https://claude.ai/code/session_01HJ7PhFNmQ19bvy9RMD6GSb
Fills the gap: eager staging at copy time, at most the current copy
kept (sweep at launch and each copy against pasteboard references,
surviving relaunch), and attachment-less fallback pastes surface a
one-shot banner naming the loss.
Claude-Session: https://claude.ai/code/session_01HJ7PhFNmQ19bvy9RMD6GSb
Fills the gap: resting zones recompute per snapshot (frozen inputs are
the dragged items' sizes and the pointer); proposals re-validate by
liveness with release-cancels-when-invalid; an emptied drag cancels
itself, partial vanishing drops survivors.
Claude-Session: https://claude.ai/code/session_01HJ7PhFNmQ19bvy9RMD6GSb
Fills the gap: on root-gone signals the bookmark re-resolves first —
success absorbs the rename transparently (watcher re-attach, URL and
window-key re-derive, full reload); only an unresolvable bookmark is
the vanished-root lock.
Claude-Session: https://claude.ai/code/session_01HJ7PhFNmQ19bvy9RMD6GSb
Fills the gap: Save All applies each raw buffer (a validation failure
cancels the switch, focus on the offender), Discard exits without
writing, Cancel keeps everything. External checkouts remain accepted
last-writer-wins per the dirty-buffer rule.
Claude-Session: https://claude.ai/code/session_01HJ7PhFNmQ19bvy9RMD6GSb
18 golden fixture boards as real on-disk trees under Fixtures/ — ten
valid/tolerated cases (rich board, interrupted create, non-UUID strays,
stray files, tombstones, tie-breaks, unknown-key order, coercion,
last-wins duplicates, board-level deleted) and eight fail-fast cases,
each pinned to its exact error reason and path; the rich board's whole
tree round-trips byte-identically. Pins two contract subtleties now
recorded in the design: tombstone flags are not inherited (renderers
walk ancestors) and last-wins resolves before order strictness. README
gains the storage-foundation feature entry. 146 tests total.
Claude-Session: https://claude.ai/code/session_018BjQRYBR6jQja3jCRi5S3A
Fills the gap: pull/push/switch/restore share the auto-committer's lock
posture with a visible waiting state; clean failures are one-shot
banner errors; the never-mutate rule exempts the app's own stamped
leftovers, which it aborts and reports.
Claude-Session: https://claude.ai/code/session_01HJ7PhFNmQ19bvy9RMD6GSb
Fills the gap: foreign commits — auto-committed agent work and agent
self-commits — push onto the in-session stack as steps, with a
pre-flight HEAD re-sync before every undo/redo. One rule in-session
and across relaunch; arriving commits clear redo.
Claude-Session: https://claude.ai/code/session_01HJ7PhFNmQ19bvy9RMD6GSb
Fills the gap: the lock spans every window sharing the store — Edit
entry, raw-source Apply, attachments, sidebar actions, checkbox toggles
disable; drags out offer copy only. Open Edit buffers stay typable with
saves suspended; held text resolves by lock cause (switch/restore
settle first, post-pull buffer-wins, returned root saves normally).
Claude-Session: https://claude.ai/code/session_01HJ7PhFNmQ19bvy9RMD6GSb
Fills the gap: restores materialize only the current-vs-target diff, so
untouched Edit sessions are unaffected; a touching restore reuses the
branch-switch Save All / Discard / Cancel step. Redo symmetric;
raw-source buffers follow the branch-switch settle treatment.
Claude-Session: https://claude.ai/code/session_01HJ7PhFNmQ19bvy9RMD6GSb
Resolves the 09-vs-03 contradiction: the principle is never-init-
unasked, scoped to template instantiation; File ▸ Duplicate carries
.git deliberately as a history fork.
Claude-Session: https://claude.ai/code/session_01HJ7PhFNmQ19bvy9RMD6GSb
Resolves the ownership contradiction: CLAUDE.md / CLAUDE.user.md (08)
and the git-init-seeded .gitignore (06) are app-claimed at board root —
the one scope on the preserved-verbatim promise agents read.
Claude-Session: https://claude.ai/code/session_01HJ7PhFNmQ19bvy9RMD6GSb
Resolves the contract-vs-practice contradiction: 04/10/11 now state the
menu home is what makes every board function keyboard-reachable and
remappable; 11's no-default rows are deliberate, not violations.
Claude-Session: https://claude.ai/code/session_01HJ7PhFNmQ19bvy9RMD6GSb
rawValue and the coercion/malformed paths now stop at the quote-aware
inline-comment boundary, so 'title: 2048 # note' coerces to "2048"
and a read-then-write no longer compounds the comment (pinned
idempotent). Strictly read-side; round-trip bytes untouched. +6 tests
(124 total).
Claude-Session: https://claude.ai/code/session_018BjQRYBR6jQja3jCRi5S3A
Duplicates: Yams' duplicate-key error is disarmed by placeholder-
renaming first occurrences and re-composing (line count preserved, so
span layout stays exact); reads take the last occurrence, set()
rewrites the winner and clears earlier twins, remove() clears all.
Top-level only — nested duplicates stay YAML errors (doc updated).
Re-splice: an inline comment on a rewritten scalar line survives, with
quote-aware detection so a # inside quotes is never a comment. +31
tests (118 total).
Claude-Session: https://claude.ai/code/session_018BjQRYBR6jQja3jCRi5S3A
Only lowercase-hex 8-4-4-4-12 folder names are lane/card candidates;
anything else is a stray — skipped with a distinct warning, never
descended, never able to fail-fast a load. UUID-shaped folders keep the
prior contract (missing index skips, malformed frontmatter fail-fasts).
Design resolution from the Redesign board. +5 tests.
Claude-Session: https://claude.ai/code/session_018BjQRYBR6jQja3jCRi5S3A
Resolves 03's internal conflict: the shown trash consumes one fixed
width unit (no width frontmatter, not resizable) and Show/Hide Trash
joins the re-divide trigger list — lane units + 1 while shown. Small
windows compress like any lane add.
Claude-Session: https://claude.ai/code/session_01HJ7PhFNmQ19bvy9RMD6GSb
Resolves 03 ▸ Motion's self-conflict: narrow keys are the sole-selected
card, the search query, and the drag's drop proposal (reflow-to-make-
room animates under it); animation-free means the replica and marquee's
1:1 cursor tracking and multi-select churn, not reflow.
Claude-Session: https://claude.ai/code/session_01HJ7PhFNmQ19bvy9RMD6GSb
Fixes 05's rationale: a cross-board move keeps its UUID (remint is
collision-only); the listing starts fresh because the destination repo
has no commits for the arriving folder.
Claude-Session: https://claude.ai/code/session_01HJ7PhFNmQ19bvy9RMD6GSb
Resolves the 01-vs-09/03 contradiction: Save as Template and File ▸
Duplicate copy the board folder as-is, GUIDs kept — the remint rule
governs item-level copies inside an existing board. Duplicate's
fork-keeps-history guarantee requires retention (copied .git history
must keep naming real paths); 03's Duplicate paragraph now says so.
Claude-Session: https://claude.ai/code/session_01HJ7PhFNmQ19bvy9RMD6GSb
Resolves the 01-vs-06 contradiction: the pure-diff composer cannot emit
'Renumber lane X' (order-only bookkeeping composes nothing), and a
renumber never happens alone — it batches with the insert/move that
exhausted the midpoint, so the commit reads as that event. 06's
bookkeeping rule now says sequence-preserving order changes are the
bookkeeping; repositioning order changes still compose Reorder.
Claude-Session: https://claude.ai/code/session_01HJ7PhFNmQ19bvy9RMD6GSb
Resolves the 02-vs-04 contradiction: foreign edits that flip deleted:
on a selected card eject it from the selection (and pending cut, which
already behaved this way), so 04's homogeneous-by-liveness invariant
holds across reloads and menu validation never sees a mixed selection.
Claude-Session: https://claude.ai/code/session_01HJ7PhFNmQ19bvy9RMD6GSb
Resolves the 06-vs-08 contradiction: 06's Lanework External fallback
demotes on true deletions only (id vanished from the board, per the
message engine's whole-board id matching), not folder moves. A bare mv
leaves the moved card unstamped, so 08 now teaches re-stamping on every
move as well as every write.
Claude-Session: https://claude.ai/code/session_01HJ7PhFNmQ19bvy9RMD6GSb
Resolves the 02-vs-04 search-filter contradiction: 04's live predicate
wins. 02's frozen-UUID transient-state list drops the result set; the
query is transient, the result set recomputes against each snapshot.
Claude-Session: https://claude.ai/code/session_01HJ7PhFNmQ19bvy9RMD6GSb
Pure tree walk: root → lanes → cards, level is position. Fail-fast with
path+reason for bad YAML, missing/malformed schema or order, newer
schema, rootless board; index-less folders skip with a collected+logged
warning; strays, hidden files, and symlinks ignored; board-level
deleted ignored with a warning; tombstones loaded and flagged. 17 tests.
Claude-Session: https://claude.ai/code/session_018BjQRYBR6jQja3jCRi5S3A
Immutable snapshot value types encoding the frontmatter tables: ItemID
wraps the exact folder name (not Foundation UUID, which uppercases);
lanes/cards carry FieldValue-typed fields, display-ordered children
including flagged tombstones, and their full FrontmatterDocument so
unknown and reserved keys ride along uninterpreted. Boards are
identified by rootURL — package folders are human-named, not UUIDs.
Claude-Session: https://claude.ai/code/session_018BjQRYBR6jQja3jCRi5S3A
Resolved on the Redesign board: raw-source Apply preserves a typed
modified-by stamp; FolderWatcher is FSEvents-only best-effort with no
iCloud/network fallback; read-only lock scope enumerated; matching
touch-ups in board-ui, card-window, accessibility docs.
Claude-Session: https://claude.ai/code/session_018BjQRYBR6jQja3jCRi5S3A
Pure ordering math: append at max+1024, head-insert at min−1024,
midpoint insertion with precision-exhaustion detection (nil on ties and
rounding onto an endpoint), renumbering to whole multiples of 1024, the
shared display-order tie-break (order, then folder name), and
tombstone-excluding overloads. 18 tests.
Claude-Session: https://claude.ai/code/session_018BjQRYBR6jQja3jCRi5S3A
Resolved on the Redesign board: post-renumber Put Back placement is
deterministic but may land among new neighbors; the commit-message
composer deliberately models the reserved labels/assignees/due trio.
Claude-Session: https://claude.ai/code/session_018BjQRYBR6jQja3jCRi5S3A