Collapse the edition split — one target, twins merged, verify-editions retired

Phase 1 of the 2026-07-30 one-app pivot (DESIGN 0bec9a6, card c3a3ddd5):
the KanbanPro target, LaneworkPro scheme, KanbanProTests module-alias
bundle, KanbanPro/ source root and scripts/verify-editions.sh retire
wholesale. project.yml reads as a single-target file again (anchors
inlined, header rewritten in tier vocabulary).

The edition twins merge: EditionTypes -> PasteboardTypes (one
UTType(exportedAs:) home — the one app owns the family types),
EditionAbout -> AboutBox (the quiet Pro signpost survives as the About
box's one line; "…in Settings" deferred until the StoreKit phase gives
it somewhere to point). InertGitTests drops its Base prefix — the
inert-.git posture is unconditional app behavior, unsubscribed and
lapsed being one state.

Entitlements gain com.apple.security.network.client, declared now and
dormant until Pro's remotes use it; no keychain access group. The App
Group key deliberately stays — it goes with AppGroup.swift in phase 2,
since pulling it first would silently drop the app into the fallback
container. README/RELEASE.md build-and-pipeline prose updated to the
one-record world; the subscription story lands with phase 3.

1893 tests in 322 suites green.

Claude-Session: https://claude.ai/code/session_01SR4XGjmBE16ZUYWpfFHXwY
This commit is contained in:
2026-07-30 17:25:50 -04:00
parent f7c8088783
commit 092300c7d2
52 changed files with 253 additions and 905 deletions
+13 -7
View File
@@ -2,9 +2,15 @@ import Foundation
import Testing
@testable import Kanban
/// **Base's inert-`.git` posture, stated against real bytes on disk** (12-editions.md Base 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 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."
///
/// 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.
///
/// 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
@@ -13,8 +19,8 @@ import Testing
/// what the app does with events and entries it is handed.
///
/// This file states the **output** claim, which no existing test covers, and which is the one the
/// edition split has to be able to demonstrate: a full session of ordinary base editing leaves
/// every byte and every mtime under `.git` exactly as it found them. It is asserted the only way
/// posture has to be able to demonstrate: 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
/// (`WriterTestSupport.swift`'s standing rule).
@@ -140,7 +146,7 @@ private struct InertGitBoard {
// MARK: - The posture
struct BaseInertGitTests {
struct InertGitTests {
@Test("A full session of ordinary edits leaves every byte and mtime under .git untouched")
func anEditingSessionNeverTouchesGit() throws {
let board = try InertGitBoard()
@@ -149,7 +155,7 @@ struct BaseInertGitTests {
let before = try snapshotSubtree(board.root, ".git")
#expect(before.count == 12, "the fixture's own shape — files, directories and the root")
// A session's worth of every write the base app can make, in one go. `.git` is at the
// A session's worth of every write the app can make, in one go. `.git` is at the
// board root, so anything that walks or renumbers the root's children walks past it.
try BoardWriter.updateIndex(inItemFolder: board.url(""), operation: .rename(title: nil)) { document in
document.set(FrontmatterKeys.title, to: .string("Renamed board"))