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
+40 -24
View File
@@ -2,24 +2,37 @@ import Foundation
import Testing
@testable import Kanban
/// **The inert-`.git` posture, stated against real bytes on disk** (12-editions.md The free tier
/// and `.git`): "any `.git` is inert opening a board that has one works normally, but the app
/// never reads history, never commits, never touches `.git` in any way."
/// **The file layer never touches `.git`, stated against real bytes on disk** every write and every
/// load the app makes leaves the whole `.git` subtree byte- and mtime-identical.
///
/// It is unconditional app behaviour, not a build-time posture: the app that ships is the only app
/// there is (12 The target, re-ruled 2026-07-30), and it runs this way for everyone without a
/// subscription and for everyone whose subscription has lapsed "unsubscribed and lapsed are one
/// state". So these tests assert what `Kanban` does, full stop; nothing here is conditioned on a
/// tier, and the git provider that will one day touch `.git` composes over its own boards.
/// ### What this file used to be, and what it is now
///
/// Two halves of that posture are already pinned elsewhere and are referenced, not repeated:
/// `FolderWatcherTests` ".git filtering" proves the watcher ignores churn under a `.git` at any
/// depth, and `BoardLoaderStrayTests.strayFilesAndHiddenEntriesAreIgnoredWithoutWarning` proves a
/// `.git` at the board root loads as an ordinary stray with no warning. Both are *input* claims:
/// what the app does with events and entries it is handed.
/// It was written as the **inert-`.git` posture**'s byte-level proof (12-editions.md The free tier
/// and `.git`, as it then read: "any `.git` is inert the app never reads history, never commits,
/// never touches `.git` in any way"). **PIVOT 2026-08-07** retired that posture outright git left
/// the paywall, so a `.git` at a board root is *live* in every tier, detection runs at every open,
/// and the auto-committer writes into exactly the directory this file's fixture plants.
///
/// This file states the **output** claim, which no existing test covers, and which is the one the
/// posture has to be able to demonstrate: a full session of ordinary editing leaves every byte and
/// The tests survive the retirement unchanged because none of them ever composed a `HistoryStore` or
/// asserted anything about a tier: they drive `BoardWriter` and `BoardLoader` directly, and what they
/// pin is that **those layers are git-agnostic**. That claim is not only still true, it is now
/// load-bearing in two places the retired posture never reached:
///
/// - **A board nobody added git to** (mode `none`) is the one board the app manages no git for, for
/// good git stays opt-in per board (06 Rules; 13-native-undo.md's header read through the
/// pivot). Nothing but this layer runs on such a board, so this layer's indifference *is* the
/// whole promise.
/// - **A `.git` the app does not manage** a repo-nested board's ancestor, a clone dropped inside a
/// card folder (which the fixture below plants deliberately) is left strictly alone by the same
/// indifference, whatever the board's own mode or the user's tier.
///
/// Two neighbouring claims are pinned elsewhere and are referenced, not repeated: `FolderWatcherTests`
/// ".git filtering" proves the watcher ignores churn under a `.git` at any depth, and
/// `BoardLoaderStrayTests.strayFilesAndHiddenEntriesAreIgnoredWithoutWarning` proves a `.git` at the
/// board root loads as an ordinary stray with no warning. Both are *input* claims: what the app does
/// with events and entries it is handed.
///
/// This file states the **output** claim: a full session of ordinary editing leaves every byte and
/// every mtime under `.git` exactly as it found them. It is asserted the only way
/// that is worth anything by snapshotting the whole `.git` subtree from the filesystem before
/// the edits and re-reading it afterwards, never through the app's own read path
@@ -109,9 +122,11 @@ private func makeGitDirectory(in fixture: WriterFixture, under parent: String) t
}
/// The board every test here edits: a root, two lanes, three cards, a `.git` at the board root
/// **and** a second one nested inside a card folder (12: "any `.git` is inert", not just the
/// root's own a repo-nested board or a clone dropped inside a card is the same promise).
private struct InertGitBoard {
/// **and** a second one nested inside a card folder. Both, deliberately: the nested one is a `.git`
/// the app never manages under any ruling (a clone dropped inside a card), and the root one is the
/// case the pivot changed it is a live repository to the *git* layer now, and still nothing at all
/// to the file layer these tests drive.
private struct UntouchedGitBoard {
let fixture: WriterFixture
let laneA: String
let laneB: String
@@ -144,12 +159,13 @@ private struct InertGitBoard {
func tearDown() { fixture.tearDown() }
}
// MARK: - The posture
// MARK: - The claim
struct InertGitTests {
@Suite("The file layer never touches `.git`")
struct UntouchedGitTests {
@Test("A full session of ordinary edits leaves every byte and mtime under .git untouched")
func anEditingSessionNeverTouchesGit() throws {
let board = try InertGitBoard()
let board = try UntouchedGitBoard()
defer { board.tearDown() }
let before = try snapshotSubtree(board.root, ".git")
@@ -205,7 +221,7 @@ struct InertGitTests {
@Test("The board loads and renders normally with a .git at its root, without a warning")
func aGitBearingBoardLoadsLikeAnyOther() throws {
let board = try InertGitBoard()
let board = try UntouchedGitBoard()
defer { board.tearDown() }
let before = try snapshotSubtree(board.root, ".git")
@@ -223,7 +239,7 @@ struct InertGitTests {
@Test("A nested .git rides along a card move byte- and mtime-verbatim")
func aNestedRepositorySurvivesACardMove() throws {
let board = try InertGitBoard()
let board = try UntouchedGitBoard()
defer { board.tearDown() }
let nested = "\(board.laneA)/\(board.cardWithRepo)/.git"
@@ -250,7 +266,7 @@ struct InertGitTests {
@Test("A copy of a card carrying a .git reproduces it verbatim and leaves the original alone")
func aCopyCarriesTheNestedRepositoryWithoutTouchingTheOriginal() throws {
let board = try InertGitBoard()
let board = try UntouchedGitBoard()
defer { board.tearDown() }
let nested = "\(board.laneA)/\(board.cardWithRepo)/.git"