Commit Graph
56 Commits
Author SHA1 Message Date
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 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 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 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