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:
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user