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:
2026-07-31 20:23:45 -04:00
parent 9119aa1e9a
commit a381fac742
11 changed files with 1709 additions and 105 deletions
+4 -4
View File
@@ -815,7 +815,7 @@ struct GitUndoSessionTests {
// would otherwise bury and the reason the step exists at all.
let cardFolder = fixture.root.appendingPathComponent("\(Ident.lane1)/\(Ident.card1)")
let token = UUID()
committer.beginEditSession(token) { cardFolder }
committer.beginCardSession(token) { cardFolder }
try fixture.item("\(Ident.lane1)/\(Ident.card1)", plain(order: "1024", title: "Half-typed"))
committer.noteReloadLanded(sawForeignChange: false)
await commitAndSettle(committer, provider)
@@ -828,7 +828,7 @@ struct GitUndoSessionTests {
// what `AppModel`'s gate hands over) goes to the plan to reconcile against the working
// tree. Passing the `<lane>/<card>` path here instead is what once made this test pass
// over a rule that did not work at all see `discardReconcilesACardIdentifiedByName`.
committer.endEditSession(token)
committer.endCardSession(token)
provider.noteDiscarded(cardFolderName: Ident.card1)
return .proceed
}
@@ -872,13 +872,13 @@ struct GitUndoSessionTests {
// the card's own folder) which no commit anywhere has ever seen.
let cardFolder = fixture.root.appendingPathComponent("\(Ident.lane1)/\(Ident.card1)")
let token = UUID()
committer.beginEditSession(token) { cardFolder }
committer.beginCardSession(token) { cardFolder }
try fixture.item("\(Ident.lane1)/\(Ident.card1)", plain(order: "1024", title: "Renamed", body: "Half-typed."))
let stray = "\(Ident.lane1)/\(Ident.card1)/attachments/sketch.txt"
try fixture.file(stray, Data("dropped mid-session".utf8))
provider.settleSessions = { _ in
committer.endEditSession(token)
committer.endCardSession(token)
// The bare id never the path. This is the whole regression.
provider.noteDiscarded(cardFolderName: Ident.card1)
return .proceed