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
+8 -6
View File
@@ -329,13 +329,15 @@ struct CommitMessageFoldingTests {
#expect(subject(of: message) == "Move 2 cards")
}
@Test("A genuinely mixed window is 'Update board' — with every event in the body")
@Test("A genuinely mixed window says so — with every event in the body")
func mixedWindowsFallBack() throws {
// Re-ruled 2026-07-31: the bare "Update board" is retired. Two cards, so no shared item and
// no name to keep "Mixed update N changes", never a shrug dressed as one thing.
let message = try compose { fixture in
try fixture.card(Ident.card1, in: Ident.lane1, order: "1024", title: "Fix logout")
try fixture.card(Ident.card3, in: Ident.lane2, order: "1024", title: "Write tests")
}
#expect(subject(of: message) == CommitMessageEngine.mixedSubject)
#expect(subject(of: message) == "Mixed update — 2 changes")
#expect(body(of: message) == [
"- Add card 'Write tests' to Doing",
"- Rename card 'Fix login' → 'Fix logout'",
@@ -419,7 +421,7 @@ struct CommitMessageBookkeepingTests {
modified: "2026-07-31T12:00:00Z"
)
}
#expect(message == CommitMessageEngine.mixedSubject)
#expect(message == CommitMessageEngine.unnamedSubject)
}
@Test("A renumber's rescale preserves sequence and composes nothing")
@@ -430,7 +432,7 @@ struct CommitMessageBookkeepingTests {
try fixture.card(Ident.card1, in: Ident.lane1, order: "16384", title: "Fix login")
try fixture.card(Ident.card2, in: Ident.lane1, order: "32768", title: "Ship it")
}
#expect(message == CommitMessageEngine.mixedSubject)
#expect(message == CommitMessageEngine.unnamedSubject)
}
@Test("An on-touch heal's backfilled kind is not an event")
@@ -441,7 +443,7 @@ struct CommitMessageBookkeepingTests {
"---\nschema: 1\ntitle: Fix login\norder: 1024\nkind: card\n---\n\n"
)
}
#expect(message == CommitMessageEngine.mixedSubject)
#expect(message == CommitMessageEngine.unnamedSubject)
}
@Test("A renumber batches with the insert that triggered it")
@@ -721,7 +723,7 @@ struct CommitMessageCommentTests {
let message = try compose { fixture in
try fixture.file(Self.thread(Ident.card1, Self.commentA), Self.commentText("Note."))
}
#expect(message != CommitMessageEngine.mixedSubject)
#expect(message != CommitMessageEngine.unnamedSubject)
#expect(!message.contains("comments/"))
}