Commit Graph
97 Commits
Author SHA1 Message Date
rzen 4b97ecf3f0 Build the welcome screen
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
2026-07-27 16:50:39 -04:00
rzen 5ebf9fb90c Implement the motion language
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
2026-07-27 15:59:59 -04:00
rzen 3be38fcb47 Realign the Cmd-N target rule — a multi-selection anchors at its last member
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
2026-07-27 15:38:34 -04:00
rzen 2090d742b9 Build the trash quasi-lane
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
2026-07-27 15:38:34 -04:00
rzen aa6aaf2a11 Build the board popover — title widget, rename, styling, git slot
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
2026-07-27 15:08:06 -04:00
rzen c6298c2e41 Build the styling system and shared style editor
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
2026-07-27 14:47:26 -04:00
rzen bea6d02d1d Realign read-side rules — width range coercion, finite order, symlink pins
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
2026-07-27 14:21:03 -04:00
rzen b4c90838b4 Build card faces with edge-accent styling
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
2026-07-27 13:48:50 -04:00
rzen b35566e0fe Build lane chrome — title bar, badge, inline rename
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
2026-07-27 08:52:24 -04:00
rzen ff3ba298f0 Build the full-visibility lane layout
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
2026-07-27 08:16:14 -04:00
rzen fccdf56cf4 Stand up the window architecture — welcome, board, card
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
2026-07-27 07:47:11 -04:00
rzen 61e18c3dfa Second Minor batch — eight implementation-session follow-ups ratified
- 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
2026-07-26 21:21:35 -04:00
rzen 3245060822 Symmetric writability probe; loader dedupes case-spelled identity twins
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
2026-07-26 21:18:48 -04:00
rzen 8cf954a8c0 Ratify the unquoted-colon rescue — read-side recovery of the classic slip
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
2026-07-26 21:17:30 -04:00
rzen adfe87fdec Batch of eleven Minor design-review fixes across the corpus
- 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
2026-07-26 21:09:23 -04:00
rzen 23e761120f Give transient UI state an explicit home in the store
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
2026-07-26 21:06:26 -04:00
rzen 8285e19497 Define how non-snapshot files commit — whole-root staging, path-shaped events
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
2026-07-26 21:05:04 -04:00
rzen 3a0a5b5e7c Pause the remote half of the git surface in abnormal repo states
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
2026-07-26 21:03:17 -04:00
rzen e618658bda Cede ⇧⌘T to Show Tab Bar — window tabbing stays enabled, Show Trash unchorded
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
2026-07-26 21:02:07 -04:00
rzen fdec965700 Give the card window's title field its Escape abandon
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
2026-07-26 20:58:45 -04:00
rzen c145cfb35a Add Restore and Permanently delete to the commit-message vocabulary
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
2026-07-26 20:57:28 -04:00
rzen 2ee88a6015 Stop ⇧-arrow extension at the trash's liveness and kind boundaries
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
2026-07-26 20:56:15 -04:00
rzen fd0f826c3e Tombstoned selections never anchor paste — the ⌘N rule returned to its precedent
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
2026-07-26 20:55:22 -04:00
rzen c26a6e3deb Settle search-field keys — stock field, swallowed Return, Tab out, staged Escape
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
2026-07-26 20:54:21 -04:00
rzen 4418b7f981 Align the identity predicate with the ratified shape-only rule
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
2026-07-26 20:53:38 -04:00
rzen 1d7449e49a Give lane selection its keyboard entry — ⌥↑ escalates; header clicks select
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
2026-07-26 20:45:10 -04:00
rzen 71e7f85328 Registry-cache the welcome row's title and icon with live write-through
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
2026-07-26 20:42:31 -04:00
rzen 747dea552d Surface write failures — banners, locks, one modal
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
2026-07-26 20:41:48 -04:00
rzen dea77c3459 Place Duplicate and Save as Template in the read-only lock's scope
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
2026-07-26 20:40:40 -04:00
rzen ce1d618117 Rank info rows in the banner precedence — in-progress pinned, signpost last
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
2026-07-26 20:30:21 -04:00
rzen c3e919be37 Make the trash sort deterministic — tie-break and unknown-date rule
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
2026-07-26 20:28:48 -04:00
rzen 764a4d4a59 Define the identity predicate as shape-only — lowercase v4 is emission, not the gate
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
2026-07-26 20:27:07 -04:00
rzen e012147476 Settle launch restoration on a live open-now registry flag
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
2026-07-26 20:22:07 -04:00
rzen 110ef1e623 Settle vanished-focus recovery when the focused card's lane vanished
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
2026-07-26 20:20:00 -04:00
rzen 7980bb692b Give Preview checkboxes their pointer-free path via the system focus model
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
2026-07-26 20:18:33 -04:00
rzen c66fae0b09 Trash respects the ancestor walk — no row under a tombstoned lane
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
2026-07-26 20:16:08 -04:00
rzen c2ff51cf88 Settle within-board lane paste as the supported lane duplicate
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
2026-07-26 20:10:42 -04:00
rzen 01ae3fb4b8 Scope modified to index.md rewrites — content provenance, not file activity
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
2026-07-26 20:08:28 -04:00
rzen 515708a11b Name the EchoLedger in 02 — settle write provenance for attribution and announcements
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
2026-07-26 20:05:54 -04:00
rzen abca29054c Convert the Writer's operation vocabulary to a closed enum
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
2026-07-26 20:03:42 -04:00
rzen 0ab0e58412 Set Lanework's version to 2.0 — the pathfinder app was 1.0
Claude-Session: https://claude.ai/code/session_018BjQRYBR6jQja3jCRi5S3A
2026-07-26 19:49:28 -04:00
rzen cb86316506 Selection liveness is effective — ancestor-walked (ratified)
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
2026-07-26 19:48:12 -04:00
rzen f0e1738964 Build the board registry and per-board app state
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
2026-07-26 19:46:18 -04:00
rzen 9818dfefbe Settle effective liveness for selection re-resolution; fix debounce attribution
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
2026-07-26 19:34:47 -04:00
rzen 68530ff569 Ratify the thrown-wholesale-operation arming rule into 02
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
2026-07-26 19:29:04 -04:00
rzen 2ba6989481 Align FolderWatcher with the newly ratified design decisions
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
2026-07-26 19:24:11 -04:00
rzen 66516d38d1 Build BoardStore — one-way flow and resilient reloads
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
2026-07-26 19:14:15 -04:00
rzen d32f03f8c9 Resolve eleven Redesign-board findings across the design corpus
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.
2026-07-26 19:06:08 -04:00
rzen cadb62564c Build FolderWatcher — FSEvents with bracketed suppression
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
2026-07-26 18:49:52 -04:00
rzen 048bb8d244 Reuse the pathfinder app icon
Claude-Session: https://claude.ai/code/session_018BjQRYBR6jQja3jCRi5S3A
2026-07-26 18:11:55 -04:00
rzen 4dcfd636a4 Ratify m2 findings into DESIGN: line endings, uneditable shapes, renumber partial, error enum
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
2026-07-26 18:06:43 -04:00
rzen b11fb9d070 Ratify three m2 write-path findings into the storage format
- 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
2026-07-26 18:06:28 -04:00
rzen 328cfb629f Write the write-path fidelity test suite
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
2026-07-26 18:04:57 -04:00
rzen 840528d14c Implement attachment import and listing
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
2026-07-26 17:48:46 -04:00
rzen fa834501b5 Implement tombstone delete, restore, and purge
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
2026-07-26 17:42:22 -04:00
rzen eb9e1e413f Implement move and copy identity semantics
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
2026-07-26 17:37:24 -04:00
rzen 8c0cec06ee Implement create operations for board, lane, and card
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
2026-07-26 17:01:09 -04:00
rzen 19f67bdb78 Build BoardWriter — atomic, minimal-touch writes
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
2026-07-26 16:49:42 -04:00
rzen a131399c02 Minor doc fixes across the corpus (ten review findings)
- 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
2026-07-26 16:30:58 -04:00
rzen 1b530e1b9a Unwritable board locations enter the read-only lock at open
Fills the gap: open-time writability probe, cause-naming banner, guide
write skipped-with-log, re-probe on reconciling reloads clears the
lock. Fail loudly, specifically, once.

Claude-Session: https://claude.ai/code/session_01HJ7PhFNmQ19bvy9RMD6GSb
2026-07-26 16:29:18 -04:00
rzen 092d226df6 Inline rename: UUID tracking, vanish discards, live filter uncarved
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
2026-07-26 16:27:51 -04:00
rzen 7ef3a64a49 Watcher recovery: reconciling reloads close every blind window
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
2026-07-26 16:26:58 -04:00
rzen 06d172a804 Clipboard staging lifecycle and loud degraded pastes
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
2026-07-26 16:25:27 -04:00
rzen 5fff6e8244 State the encoding and line-ending contract for index.md
Fills the gap, matching the built engine: strict UTF-8 no-BOM reads
(undecodable or BOM'd files fail fast loudly), BOM-less writes,
per-line ending preservation (CRLF stays CRLF), final newline
preserved, app-created files LF. Agent guide teaches the contract.

Claude-Session: https://claude.ai/code/session_01HJ7PhFNmQ19bvy9RMD6GSb
2026-07-26 16:24:41 -04:00
rzen 3d7457d80f Drop semantics under mid-drag foreign reloads
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
2026-07-26 16:23:03 -04:00
rzen 498d23e15e Renamed board root re-resolves by file identity mid-session
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
2026-07-26 16:22:12 -04:00
rzen 9fb5845a5b Raw-source buffers join the branch-switch settle step
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
2026-07-26 16:21:26 -04:00
rzen 7470255886 Write the loader fixture suite, valid and malformed
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
2026-07-26 16:20:47 -04:00
rzen e6c34c0ccf Failure paths for app-initiated git operations
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
2026-07-26 16:20:25 -04:00
rzen 607e1b837b The undo stack is HEAD's first-parent ancestry, live
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
2026-07-26 16:18:57 -04:00
rzen 03ec2f0879 Extend the read-only lock to card-window write paths
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
2026-07-26 16:16:49 -04:00
rzen 5af3680070 Undo restore gates on save-or-discard when its diff touches a session card
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
2026-07-26 16:15:32 -04:00
rzen 1f469cebcb Scope 09's no-silent-git rule to instantiation
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
2026-07-26 16:12:00 -04:00
rzen 0b779470f2 Name the app-claimed board-root files in 01's stray-tolerance rule
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
2026-07-26 16:11:28 -04:00
rzen 1337265dd7 Menu item mandatory, default chord where warranted — contract reworded
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
2026-07-26 16:10:37 -04:00
rzen 5a459f0ee4 Exclude inline comments from raw value reads
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
2026-07-26 16:10:32 -04:00
rzen 4085dd7a46 Engine — duplicate keys last-wins, inline-comment re-splice
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
2026-07-26 16:07:06 -04:00
rzen 31f7691062 Gate level detection on UUID folder-name shape
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
2026-07-26 16:03:39 -04:00
rzen 30af21a66e Settle nine design-review items
Storage: UUID-shape gates level detection; readable-but-uneditable
frontmatter shapes load and refuse writes; duplicate keys last-wins;
inline-comment re-splice; null reads as missing. Architecture: the
banner surface specified (origin-window host, dismiss vs heal,
precedence stack, tones incl. in-progress rows and safe-copy Cancel);
launch/window lifecycle (restoration preference, welcome policy,
row-level restore failures); bracketed operations lock writes at the
failed-reload lock's scope.

Claude-Session: https://claude.ai/code/session_018BjQRYBR6jQja3jCRi5S3A
2026-07-26 16:03:00 -04:00
rzen ca22d5459c Trash quasi-lane spans a fixed width unit; Show Trash re-divides
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
2026-07-26 15:59:52 -04:00
rzen 8858a38d7d Name the drop proposal as drag reflow's animation key
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
2026-07-26 15:57:57 -04:00
rzen 22e53b6c88 History trail starts fresh for repo reasons, not identity reasons
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
2026-07-26 15:54:15 -04:00
rzen 18a23280cd Whole-board copies keep GUIDs — carve-out from the copies-remint rule
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
2026-07-26 15:53:29 -04:00
rzen 94e795aa89 Renumber commits as the triggering event, not a standalone message
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
2026-07-26 15:51:31 -04:00
rzen 0871b96789 A liveness flip is a vanish — selection re-resolves by UUID and liveness side
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
2026-07-26 15:50:10 -04:00
rzen 4b20f8499b Moves are not deletions for stamp attribution; re-stamp on move
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
2026-07-26 15:49:14 -04:00
rzen 8e7c565e07 Adopt read-side coercion for schema-owned fields
Design-review resolutions: schema-owned display fields coerce where a
sensible reading exists (wrong-type scalars read as source text, width
accepts exact-integer strings/doubles) and default where none does;
null reads as missing; duplicate keys last-one-wins and inline-comment
re-splicing recorded in the design (engine change follows). Strict
schema/order fail-fast unchanged. 85 tests green.

Claude-Session: https://claude.ai/code/session_018BjQRYBR6jQja3jCRi5S3A
2026-07-26 15:47:33 -04:00
rzen a6f4960492 Search result set is derived, not frozen transient state
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
2026-07-26 15:44:38 -04:00
rzen 95418a2670 Build BoardLoader — fail-fast fractal tree loading
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
2026-07-26 15:40:46 -04:00
rzen c7cc97aeec Define the board, lane, and card model types
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
2026-07-26 15:40:45 -04:00
rzen cb6f6100a7 Build the frontmatter engine with byte-perfect round-trip
Yams validates and reads; writes are surgical line-span edits over
retained raw text, so untouched bytes — unknown keys, comments, odd
formatting, bodies — round-trip identically by construction. Strict
schema/order readers distinguish valid/missing/malformed for the
loader's fail-fast; lenient fields preserve malformed values verbatim;
modified-by is schema-owned. Adds the Yams package and the fixture
folder-reference wiring. 44 tests (105 cases).

Claude-Session: https://claude.ai/code/session_018BjQRYBR6jQja3jCRi5S3A
2026-07-26 15:26:12 -04:00
rzen 954c4b351d Refine design — raw-source Apply carve-out, watcher scope, lock scope
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
2026-07-26 15:26:12 -04:00
rzen 4e016fe8fe Implement Ranks — gapped fractional ordering
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
2026-07-26 15:12:26 -04:00
rzen 9472198848 Refine design — Put Back edge case, reserved-key commit messages
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
2026-07-26 15:04:00 -04:00
rzen c4d4fbe663 Set up repo hygiene — README, CHANGELOG, versioning
README with a maintained key-features section, CHANGELOG.md stub in the
portfolio's IndieAbout format, and a post-build phase stamping
CFBundleVersion (git commit count), BuildDate, and BuildHash into the
app and dSYM Info.plists on every build.

Claude-Session: https://claude.ai/code/session_018BjQRYBR6jQja3jCRi5S3A
2026-07-26 15:01:35 -04:00
rzen 01eabbe796 Scaffold the Xcode project — XcodeGen, Swift 6, sandboxed
XcodeGen project (codename Kanban, display name Lanework, bundle id
dev.rzen.indie.Kanban): macOS 26+ app target with Swift 6 strict
concurrency, app sandbox with user-selected file access and app-scoped
bookmark entitlements, plus unit-test (Swift Testing) and XCUITest
targets wired into the Kanban scheme.

Claude-Session: https://claude.ai/code/session_018BjQRYBR6jQja3jCRi5S3A
2026-07-26 15:00:31 -04:00
rzen b0763c3c82 Add design corpus and wishlist
Twelve design docs (00-vision through 11-command-nexus) plus assets and
the wishlist, authored ahead of implementation.

Claude-Session: https://claude.ai/code/session_018BjQRYBR6jQja3jCRi5S3A
2026-07-26 14:58:16 -04:00