The subscription machinery leaves the code — Kanban/Tier excised, StoreKit wiring unwound

The 2026-08-08 one-version ruling (12-editions.md ▸ PIVOT 2026-08-08) carried out: Kanban/Tier/
deleted wholesale (Tier, ProEntitlement, ProProducts, ProStorefront, the never-rendered
ProSettingsSection) with TierTests and Configuration.storekit, whose project.yml resource entry
and scheme storeKitConfiguration go with it. AppModel loses the entitlement, the currentTier
seam, BoardSession.tier, and the purchase flow's reopenOpenBoards (its only caller was the
storefront); AppDelegate's launch keeps only the appearance application. The three tests
pinning the recorded tier and the reopen are deleted with their subject. The network-client
entitlement stays — the sync capability to come needs it regardless — and the HistoryProviding
seam stands untouched. 2,686 unit tests green (2,707 minus the 21 that tested what left).

Claude-Session: https://claude.ai/code/session_014PtZdPwqZuqEDLc6wZMtEy
This commit is contained in:
2026-08-08 13:18:54 -04:00
parent 871083e5ca
commit e1f89d9cf9
15 changed files with 48 additions and 1465 deletions
+5 -14
View File
@@ -16,24 +16,15 @@ final class AppDelegate: NSObject, NSApplicationDelegate {
private static let logger = Logger(subsystem: "dev.rzen.indie.Kanban", category: "app-delegate")
/// Starts the Pro entitlement's transaction listener (12-editions.md The entitlement).
/// Applies the stored appearance override to `NSApp` (03-board-ui.md Toolbar).
///
/// **Here rather than in `AppModel.init`**, and the distinction matters: a unit-test host *is*
/// this app, so `KanbanApp.init()` and therefore `AppModel.init` run on every test launch
/// (`AppStateHome.isUnitTestHost`). Building the entitlement there costs one `UserDefaults` read;
/// acquiring a StoreKit listener there would give every test run a live `Transaction.updates`
/// subscription for no reason. Launch is the honest home for a listener, and this is the app's.
///
/// It starts nothing the board-open path waits on: the listener writes cached facts that a
/// *later* composition may read, and never reaches into a session that is already open
/// (`ProEntitlement`).
///
/// **The appearance override applies here too, for the same reason.** `AppearanceStore.init`
/// only reads; this is the one call that hands its answer to `NSApp` the global side effect
/// `KanbanApp.init` must not carry, since a unit-test host runs that `init` on every launch
/// (`AppearanceStore.applyCurrent`).
/// (`AppStateHome.isUnitTestHost`). `AppearanceStore.init` only reads a default; this is the one
/// call that hands its answer to `NSApp` the global side effect `KanbanApp.init` must not
/// carry (`AppearanceStore.applyCurrent`). Launch is the honest home for it, and this is the
/// app's.
func applicationDidFinishLaunching(_ notification: Notification) {
appModel?.entitlement.start()
appModel?.appearance.applyCurrent()
}