Give card windows their own undo stacks and coarsen the close

Phase B of the two-level undo card: every card-window gesture — comment
post/delete/edit, body Edit sessions, style and details changes —
registers fine-grained on the window's own stack (window.undoManager
answers with it; board ⌘Z never sees mid-session card steps; an empty
window stack beeps, never falls through). Window close folds the stack
into one coarse values-based board step ("Edit card 'X'") — per-target
per-field later-wins merge, so foreign mid-session writes stay out by
construction, a no-net-change session registers nothing, and any stale
component skips the whole step. The comments/.trash purge defers with
the coarse step via a step-retirement seam on the providers: it runs
when the step leaves the board stack or the board session ends; the git
provider retires dropped steps on register, which keeps Pro's
purge-at-close-flush structural with no tier check. Interim on git
boards: gestures still auto-commit per debounce until phase C's
close-flush commit.

2432 tests in 418 suites green.

Claude-Session: https://claude.ai/code/session_01CqjXB7ASoWtbyoGod68k97
This commit is contained in:
2026-07-31 19:39:50 -04:00
parent c0c741fe62
commit 71664dab02
23 changed files with 668 additions and 124 deletions
+6 -3
View File
@@ -339,9 +339,12 @@ public final class AppModel {
/// open-now flag without matching by identity a second time.
public let recordID: UUID
/// This board's undo/redo substrate **one stack per board session, never per window**
/// (13-native-undo.md Rules). It lives here for the store's reason exactly: the session is
/// what every window over this board shares, and "undo is board-local".
/// This board's undo/redo substrate **the board half of 13-native-undo.md Rules' two
/// levels** (re-ruled 2026-07-31): one stack per board session, carrying board-surface
/// gestures and the one coarse step each card window's close registers. A card window's own
/// fine-grained stack is not here and never was the session's (`CardWindowUndo`, held by the
/// window). It lives here for the store's reason exactly: the session is what every window
/// over this board shares, and "undo is board-local".
///
/// Which implementation it is, is the tier's answer and nobody else's
/// (12-editions.md The provider seam) see `AppModel.makeHistoryProvider`.