Make the card window the commit unit on Pro boards
Phase C of the two-level undo card: the committer stages around the
whole open card folder — comments included — so gestures in an open
window never land in interim commits; window close flushes the session
as one semantically-named commit ("Edit card 'X'" with the thread as
body bullets, "Mixed update — N changes to card 'X'" when events mix),
with the two-commit foreign/user split preserved and the
comments/.trash purge riding the same bracket. Comment gestures lose
their per-gesture commits structurally (they write inside the held
folder). Branch-switch settle releases every window's staging before
checkout and re-arms on resume.
Fixes two latent pro-m1 defects: the committer was composed without
the store's EchoLedger, so every production commit classified foreign
and was authored Lanework External; and interim flushes dropped
harvest receipts they had not spent, unvouching the session's own
writes at close. Also lands 06's mixed-subject re-ruling (the retired
"Update board" fallback) and phase B's two files missed by the
previous commit's pathspec.
2444 tests in 422 suites green.
Claude-Session: https://claude.ai/code/session_01CqjXB7ASoWtbyoGod68k97
This commit is contained in:
@@ -634,14 +634,12 @@ struct CardWindowHost: View {
|
||||
bodyPresentation.beginEdits = { [session] in
|
||||
session.body.beginEditSession()
|
||||
}
|
||||
// **The stage-around registry's one wire** (06-history-undo.md ▸ Rules ▸ Auto-commit). The
|
||||
// buffer announces its session boundary, the app model knows which board this card belongs
|
||||
// to, and the committer knows what staging is; this line is the join, and it is the only
|
||||
// place all three are in scope. A free-tier board — or any board with no repository — has no
|
||||
// committer, so `setEditSession` records nothing and the buffer never learns the difference.
|
||||
session.body.editSessionDidChange = { [appModel, ref] isEditing in
|
||||
appModel.setEditSession(isEditing, for: ref)
|
||||
}
|
||||
// **No stage-around wire here any more** (06-history-undo.md ▸ Rules ▸ Auto-commit, widened
|
||||
// 2026-07-31 — recorded because its absence is the change): the Edit→Preview flip used to open
|
||||
// and close the committer's exclusion, and the unit is now the *window*, so the exclusion is
|
||||
// opened by `AppModel.registerCardWindow` and released by `unregisterCardWindow` — after the
|
||||
// session's own last writes. A flip that still moved it would un-hold the folder in the middle
|
||||
// of a session whose comment posts and draft saves are supposed to be inside one commit.
|
||||
Self.configureRawSource(
|
||||
rawSource,
|
||||
body: session.body,
|
||||
@@ -949,15 +947,22 @@ struct CardWindowHost: View {
|
||||
/// Leaves the session and lets the store go.
|
||||
///
|
||||
/// The release rides **behind** the session's end rather than beside it: a session that has
|
||||
/// something to commit (m6) needs the store it is committing through, and a refcount that hit
|
||||
/// zero first would have stopped the watcher underneath it. In m4 the hook is a no-op and the
|
||||
/// ordering costs one run-loop turn — the point is that the shape is already right.
|
||||
/// something to commit needs the store it is committing through, and a refcount that hit zero
|
||||
/// first would have stopped the watcher underneath it.
|
||||
///
|
||||
/// **Unregistering rides behind it too** (06-history-undo.md ▸ Rules ▸ Auto-commit: "window close
|
||||
/// flushes the session as one commit"), which is new in this milestone and is the whole ordering
|
||||
/// the one-commit rule rests on: unregistering is what releases the committer's stage-around, and
|
||||
/// releasing it before `endSession()` had written the body's last keystrokes, posted the draft and
|
||||
/// purged `comments/.trash/` would leave a debounce free to fire over a half-finished session —
|
||||
/// two commits where the design promises one. The board's own close flush drives the same two
|
||||
/// steps in the same order through `CloseFlushCoordinator`, one window at a time.
|
||||
private func finish() {
|
||||
guard case let .open(store) = phase else { return }
|
||||
phase = .closing
|
||||
appModel.unregisterCardWindow(ref)
|
||||
Task { @MainActor in
|
||||
await session.endSession()
|
||||
appModel.unregisterCardWindow(ref)
|
||||
appModel.storeRegistry.release(store)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user