The tier axis comes out of the git stack — compose unconditional, postures mode-driven

HistoryStore.compose(boardRoot📒) returns non-optional and runs for
every session — the nil the gate produced was the only nil it ever had.
makeHistoryProvider is a one-axis decision: git-mode boards bind the git
provider, everything else native, in every tier; Session.tier stays
recorded, dormant. BoardGitSection shrinks to the four mode postures
(.absent and .proPointer die, BoardGitNote and the .git probe with them);
every board carries all three popover tabs (BoardInfoTab.available
retired); the titlebar branch shows on any git-mode board; the settings
sheet and card History section stop reading tier. InertGitTests is
repurposed as UntouchedGitTests — the file layer still never opens .git,
now load-bearing for mode-none boards. The accessibility audit reaches the
settings sheet at last: the fixture board hosts it in every tier, so the
free-fixture disabled-row test becomes an open-and-audit test.

Claude-Session: https://claude.ai/code/session_01SR4XGjmBE16ZUYWpfFHXwY
This commit is contained in:
2026-08-07 20:21:28 -04:00
parent da0d7fd2d7
commit 798a8bac73
32 changed files with 830 additions and 904 deletions
+62 -50
View File
@@ -39,7 +39,7 @@ private func makeBoard() throws -> WriterFixture {
private func makeGitBoard() async throws -> (fixture: WriterFixture, git: HistoryStore, ledger: EchoLedger) {
let fixture = try makeBoard()
let ledger = EchoLedger()
let git = try #require(HistoryStore.compose(boardRoot: fixture.root, tier: .pro, ledger: ledger))
let git = HistoryStore.compose(boardRoot: fixture.root, ledger: ledger)
#expect(await git.addGit())
return (fixture, git, ledger)
}
@@ -998,32 +998,36 @@ struct GitUndoSessionTests {
// MARK: - The provider binding
/// **The provider follows the board, not the tier alone** (re-ruled 2026-07-31 12-editions.md
/// The provider seam; 13-native-undo.md's header; 06-history-undo.md Rules), stated as the
/// matrix it is: **boards without app-managed git repo-nested included bind the native stack in
/// *every* tier** ("an upgrade never removes undo"), and a Pro git board binds the git provider.
/// There is no third answer any more.
/// **The provider follows the board and since PIVOT 2026-08-07, the board alone** (re-ruled
/// 2026-07-31 12-editions.md The provider seam; 13-native-undo.md's header; 06-history-undo.md
/// Rules; then 12 PIVOT 2026-08-07, git leaves the paywall), stated as the matrix it is: a
/// **git-mode board binds the git provider** and **every other board binds the native stack**
/// repo-nested and unverifiable included in *every* tier. There is no third answer and no second
/// axis.
///
/// ### The repo-nested row stopped being an exception
/// ### What the pivot changed here, and what it did not
///
/// `HistoryStore.compose` returns `nil` off Pro, so a free-tier session never detects a mode at all
/// and cannot tell a repo-nested board from a plain one which is not an omission but 12 The free
/// tier and `.git` verbatim: "opening a board that has one (a formerly-subscribed user's board, a
/// 1.x board, **a repo-nested board**) works normally files read and write as on any board,
/// **native undo runs**". Pro used to answer differently on the same board, which made subscribing
/// *remove* Z from it; the re-ruling of 2026-07-31 retired that case outright "leave strictly
/// alone concerns *git*, and this stack never touches git memory-only, journal-free,
/// session-scoped" (13's header) so the two tiers now agree on every board there is, and the tests
/// below pin both halves of that agreement.
/// The 2026-07-31 re-ruling had already taken the tier out of every row but one: it still decided
/// whether git was on the table at all, because `HistoryStore.compose` returned `nil` off Pro, so a
/// free session never detected a mode and could not tell a repo-nested board from a plain one. The
/// pivot removes that last row "every tier composes the git stack on git-mode boards exactly as
/// Pro did" so a free-tier session on a git board now binds the *git* provider, which is the
/// inversion `freeTierBindsGitOnAGitBoard` below pins.
///
/// What survives untouched is the gitless side: a board nobody added git to detects `none` and binds
/// the native stack, for good, because git stays **opt-in per board** (06 Rules; 13's header read
/// through the pivot). And the repo-nested row keeps the 2026-07-31 reasoning that retired it as an
/// exception "leave strictly alone concerns *git*, and this stack never touches git memory-only,
/// journal-free, session-scoped" (13's header).
@MainActor
@Suite("Git undo ▸ which board gets a provider")
struct GitUndoBindingTests {
@Test("Free tier binds the native stack everywhere, git or not")
func freeTierIsNativeEverywhere() async throws {
@Test("Free tier on a git board binds the git provider — git left the paywall")
func freeTierBindsGitOnAGitBoard() async throws {
let fixture = try makeBoard()
defer { fixture.tearDown() }
let seed = try #require(HistoryStore.compose(boardRoot: fixture.root, tier: .pro))
let seed = HistoryStore.compose(boardRoot: fixture.root)
#expect(await seed.addGit())
let (model, tearDown) = try makeModel()
@@ -1033,16 +1037,20 @@ struct GitUndoBindingTests {
let ref = try openBoard(model, at: fixture.root)
let session = try #require(model.session(for: ref))
#expect(session.git == nil, "the free tier composes no git state at all")
#expect(session.history is NativeHistoryProvider)
#expect(session.undoManager.canUndo == false, "empty, not absent")
// **PIVOT 2026-08-07** (12-editions.md), and the exact inversion of what this test used to
// pin: `session.git == nil` and the native stack, because the free tier composed no git state
// at all and this board's `.git` was inert. It is live now, in every tier.
#expect(session.git != nil, "every session composes a git state")
#expect(session.gitMode == .git)
#expect(session.history is GitHistoryProvider)
#expect(session.tier == .free, "recorded, and consulted by nothing on this path")
}
@Test("Pro on a git board binds the git provider")
func proOnAGitBoardBindsGit() async throws {
let fixture = try makeBoard()
defer { fixture.tearDown() }
let seed = try #require(HistoryStore.compose(boardRoot: fixture.root, tier: .pro))
let seed = HistoryStore.compose(boardRoot: fixture.root)
#expect(await seed.addGit())
let (model, tearDown) = try makeModel()
@@ -1055,8 +1063,8 @@ struct GitUndoBindingTests {
#expect(session.history is GitHistoryProvider)
}
@Test("Pro on a mode-none board binds the native stack — an upgrade never removes undo")
func proOnAPlainBoardBindsTheNativeStack() throws {
@Test("A mode-none board binds the native stack — opt-in is what keeps it there")
func aPlainBoardBindsTheNativeStack() throws {
let fixture = try makeBoard()
defer { fixture.tearDown() }
let (model, tearDown) = try makeModel()
@@ -1066,14 +1074,16 @@ struct GitUndoBindingTests {
let ref = try openBoard(model, at: fixture.root)
let session = try #require(model.session(for: ref))
// The board is Pro's it composed a git state and detected a mode and the mode is what
// chose the substrate: "gitless boards bind the native undo stack in every tier" (12).
#expect(session.git != nil, "Pro composes a git state even where there is no repository")
// The board composed a git state and detected a mode, and the mode is what chose the
// substrate: "gitless boards bind the native undo stack in every tier" (12). Since PIVOT
// 2026-08-07 the *only* thing keeping a board on this row is that nobody added git to it
// opt-in per board is exactly what the pivot left standing (06 Rules; 13's header).
#expect(session.git != nil, "a git state is composed even where there is no repository")
#expect(session.gitMode == .none)
#expect(session.history is NativeHistoryProvider)
// And it is a *working* stack, not a placeholder: the same command surface a free-tier
// session gets, which is what "a user subscribing relearns nothing" means here.
// And it is a *working* stack, not a placeholder: the same command surface every other
// gitless board gets, which is what "a user relearns nothing" means here.
let log = StepLog()
#expect(session.undoManager.canUndo == false, "empty, not absent")
session.history?.register(log.step("Move Card"))
@@ -1083,8 +1093,8 @@ struct GitUndoBindingTests {
#expect(log.crossings == ["undo Move Card"])
}
@Test("Pro on a repo-nested board binds the native stack — the no-undo case is retired")
func proOnARepoNestedBoardBindsTheNativeStack() throws {
@Test("A repo-nested board binds the native stack — the no-undo case is retired")
func aRepoNestedBoardBindsTheNativeStack() throws {
let outer = try WriterFixture()
defer { outer.tearDown() }
// A repository at the *parent*, with the board inside it the nested posture.
@@ -1101,15 +1111,15 @@ struct GitUndoBindingTests {
let ref = try openBoard(model, at: boardRoot)
let session = try #require(model.session(for: ref))
// The mode is detected Pro walks the ancestors and it no longer decides Z: "Pro binds it
// on mode-none **and repo-nested** boards alike what repo-nested denies is app-managed
// history, never Z" (13's header, re-ruled 2026-07-31).
// The mode is detected the ancestor walk runs and it no longer decides Z: "mode-none
// **and repo-nested** boards alike bind it what repo-nested denies is app-managed history,
// never Z" (13's header, re-ruled 2026-07-31).
#expect(session.gitMode == .repoNested)
#expect(session.history is NativeHistoryProvider)
#expect(session.undoManager.canUndo == false, "empty, not absent")
// A working stack, exactly as the free tier's on this same board which is the whole point
// of the re-ruling: the two tiers answer alike here now.
// A working stack, exactly as on the same board under any other tier see
// `freeTierOnARepoNestedBoardIsNativeToo`, which now detects the same mode this one does.
let log = StepLog()
session.history?.register(log.step("Move Card"))
#expect(session.undoManager.canUndo)
@@ -1142,12 +1152,12 @@ struct GitUndoBindingTests {
defer { tearDown() }
let git = HistoryStore(boardRoot: fixture.root, mode: .unverifiable, ledger: EchoLedger())
let provider = model.makeHistoryProvider(store, .pro, git)
let provider = model.makeHistoryProvider(store, git)
#expect(provider is NativeHistoryProvider)
}
@Test("The free tier's repo-nested board still binds the native stack — it never detects one")
@Test("The free tier's repo-nested board binds the native stack too — now that it detects one")
func freeTierOnARepoNestedBoardIsNativeToo() throws {
let outer = try WriterFixture()
defer { outer.tearDown() }
@@ -1164,15 +1174,17 @@ struct GitUndoBindingTests {
let ref = try openBoard(model, at: boardRoot)
let session = try #require(model.session(for: ref))
// 12 The free tier and `.git` names this board by hand: "opening a board that has one
// a repo-nested board works normally native undo runs". The tier composes no git state
// at all, so there is nothing here that *could* tell this board from a plain one the inert
// posture made structural rather than remembered.
#expect(session.git == nil)
#expect(session.gitMode == .none, "no detection ran; the session reports the tier's one mode")
// The answer is unchanged and the *reason* is new. Before PIVOT 2026-08-07 the free tier
// composed no git state, so nothing here could tell this board from a plain one and native
// undo followed by default "opening a board that has one a repo-nested board works
// normally native undo runs" (12 The free tier and `.git`, now retired). Detection runs
// in every tier now, sees the enclosing repository, and binds native anyway, because what
// repo-nested denies is app-managed history and never Z (13's header, re-ruled 2026-07-31).
#expect(session.git != nil, "detection runs here now")
#expect(session.gitMode == .repoNested, "and it sees the enclosing repository")
#expect(session.history is NativeHistoryProvider)
// Pro on this same board now answers identically (above) the two tiers agree, which is what
// the re-ruling of 2026-07-31 bought: an upgrade never removes undo from *any* board.
// The same board under `.pro` answers identically (above) one matrix, no tier column.
#expect(session.git?.committer == nil, "and the enclosing repository is still left alone")
}
@Test("Add-git swaps the substrate — the native stack is discarded, the git trail seeded")
@@ -1265,7 +1277,7 @@ struct GitOperationFailureBannerTests {
func aFailedRestorePostsAFailureRow() async throws {
let fixture = try makeBoard()
defer { fixture.tearDown() }
let seed = try #require(HistoryStore.compose(boardRoot: fixture.root, tier: .pro))
let seed = HistoryStore.compose(boardRoot: fixture.root)
#expect(await seed.addGit())
let (model, tearDown) = try makeModel()
@@ -1302,7 +1314,7 @@ struct GitOperationFailureBannerTests {
func theSwitcherReportsFailureAndRecoveryDifferently() async throws {
let fixture = try makeBoard()
defer { fixture.tearDown() }
let seed = try #require(HistoryStore.compose(boardRoot: fixture.root, tier: .pro))
let seed = HistoryStore.compose(boardRoot: fixture.root)
#expect(await seed.addGit())
let (model, tearDown) = try makeModel()
@@ -1336,7 +1348,7 @@ struct GitUndoRoutingTests {
func textEditingWins() async throws {
let fixture = try makeBoard()
defer { fixture.tearDown() }
let seed = try #require(HistoryStore.compose(boardRoot: fixture.root, tier: .pro))
let seed = HistoryStore.compose(boardRoot: fixture.root)
#expect(await seed.addGit())
let (model, tearDown) = try makeModel()