Do-nothing guards read the pending truth — never-trust-memory is the render path's scope
Gap #11 ruled into 02 ▸ One-way flow: between a write's bracket and its echo the snapshot describes the past, so a guard deciding whether a write would change disk baselines on the snapshot as amended by this store's own in-flight writes — a pending-value record of the write path's own, never the EchoLedger. Relative gestures (step one width unit) resolve their base against the same amended truth, and the undo step's recorded prior reads it too. The amendment dies with its echo; failed writes never enter it. Coalescing weighed and set aside — one gesture, one write, one commit. Implementation filed on the Implementation board. Claude-Session: https://claude.ai/code/session_014PtZdPwqZuqEDLc6wZMtEy
This commit is contained in:
@@ -20,7 +20,7 @@ BoardStore (one per open board, @Observable, MainActor)
|
|||||||
SwiftUI views (board window + card windows share the store)
|
SwiftUI views (board window + card windows share the store)
|
||||||
```
|
```
|
||||||
|
|
||||||
One-way flow: **files → watcher → loader → store → views**. User actions go through a Writer that mutates files; the change comes back around through the watcher like any external edit. The app trusts its own writes no more than anyone else's — **for rendering** (settled scope): the snapshot is only ever built from disk, never from memory of what the app meant to write — this is what makes external editors and agents first-class. Provenance is a separate, downstream concern: the **EchoLedger** (Components below) remembers what the app wrote so commit attribution (06-history-undo.md) and VoiceOver announcements (10-accessibility.md) can tell the app's own echo from a foreign change — without the render path ever trusting memory over disk.
|
One-way flow: **files → watcher → loader → store → views**. User actions go through a Writer that mutates files; the change comes back around through the watcher like any external edit. The app trusts its own writes no more than anyone else's — **for rendering** (settled scope): the snapshot is only ever built from disk, never from memory of what the app meant to write — this is what makes external editors and agents first-class. Provenance is a separate, downstream concern: the **EchoLedger** (Components below) remembers what the app wrote so commit attribution (06-history-undo.md) and VoiceOver announcements (10-accessibility.md) can tell the app's own echo from a foreign change — without the render path ever trusting memory over disk. **The write path's do-nothing guards read the pending truth** (ruled 2026-08-06): never-trust-memory is the *render* path's scope, and it does not extend to a guard deciding whether a write would change disk. Between a write's bracket and its echo the snapshot describes the past — a guard comparing an asked-for value against it answers the wrong question, and a fast gesture pair silently loses its second half (the found case: two lane resizes released within one echo, A→B→A — the second compares A against the stale snapshot's A, writes nothing, and the first's echo settles the board at B, the width the user last dragged away from). So a do-nothing guard's baseline is the snapshot **as amended by this store's own in-flight writes** — the value it last wrote to that field and has not yet seen echo (a small pending-value record of the write path's own, *not* the EchoLedger, whose feeds-attribution-only charter stands); a gesture whose meaning is relative (step one width unit) resolves its base against the same amended truth, or a fast double-step loses its second press to the same staleness; and the undo step's recorded prior reads it too, or its inverse restores a state that never was. The amendment dies with its echo — a landed snapshot agreeing with the write clears it — and a failed or refused write never enters it (no echo is coming; the snapshot is still the truth). Per-gesture coalescing was weighed and set aside: one gesture is one write, one echo, one commit (the style batch's rule), and merging two gestures' writes would merge their commits.
|
||||||
|
|
||||||
The **one named exception** is transient UI state rendering things that don't exist on disk — concretely the **new-card placeholder** (04-interactions.md): the inline editor for a card being created renders as a pseudo-card overlaid on the snapshot, with no disk presence and no UUID until the title commits. Commit creates the folder through the Writer and round-trips through the watcher like any write — the placeholder stays visible until the real card arrives, then hands off. **The handoff must read as one arrival** (settled): the placeholder renders at the arriving card's exact geometry — same slot, same size, same chrome — so the identity swap's cross-fade is imperceptible; two view identities are fine, two visible objects are not (no matched-geometry machinery across the overlay/snapshot boundary, just matched rendering). Abandoning (Escape, empty commit, click-away) discards it; disk was never touched. **A failed create discards it too** (settled): if the Writer create throws after the title commits, the create flow discards the placeholder — the failure surfaces as the ordinary one-shot banner (Write-failure surfacing below), and the overlay never waits for a card that cannot arrive. **Starting a new creation while a placeholder is open is a click-away for the draft** (settled): the open draft discards per its rule and the new placeholder begins — and ⌘N can't even reach this case (board commands disable while the editor is focused, 04-interactions.md), so only pointer paths do. Watcher reloads swap the snapshot *underneath* the overlay (like selection surviving a reload); if the placeholder's lane vanished in the reload, it is discarded — consistent with card windows dismissing when their card is deleted. Everything durable still round-trips through files.
|
The **one named exception** is transient UI state rendering things that don't exist on disk — concretely the **new-card placeholder** (04-interactions.md): the inline editor for a card being created renders as a pseudo-card overlaid on the snapshot, with no disk presence and no UUID until the title commits. Commit creates the folder through the Writer and round-trips through the watcher like any write — the placeholder stays visible until the real card arrives, then hands off. **The handoff must read as one arrival** (settled): the placeholder renders at the arriving card's exact geometry — same slot, same size, same chrome — so the identity swap's cross-fade is imperceptible; two view identities are fine, two visible objects are not (no matched-geometry machinery across the overlay/snapshot boundary, just matched rendering). Abandoning (Escape, empty commit, click-away) discards it; disk was never touched. **A failed create discards it too** (settled): if the Writer create throws after the title commits, the create flow discards the placeholder — the failure surfaces as the ordinary one-shot banner (Write-failure surfacing below), and the overlay never waits for a card that cannot arrive. **Starting a new creation while a placeholder is open is a click-away for the draft** (settled): the open draft discards per its rule and the new placeholder begins — and ⌘N can't even reach this case (board commands disable while the editor is focused, 04-interactions.md), so only pointer paths do. Watcher reloads swap the snapshot *underneath* the overlay (like selection surviving a reload); if the placeholder's lane vanished in the reload, it is discarded — consistent with card windows dismissing when their card is deleted. Everything durable still round-trips through files.
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user