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:
@@ -2,6 +2,12 @@ import Foundation
|
||||
import Testing
|
||||
@testable import Kanban
|
||||
|
||||
/// **⚠ Retired by the one-app collapse — one-app collapse phase 2.** 12-editions.md ▸ App-side
|
||||
/// state (re-ruled 2026-07-30) removes the App Group wholesale: one app, one sandbox, one grant, one
|
||||
/// open-now flag. This whole file is the two-app arrangement's proof, and it retires with the code
|
||||
/// it pins — it is kept green in the meantime rather than deleted ahead of the machinery, because
|
||||
/// the machinery is what still ships.
|
||||
///
|
||||
/// **App-side state in the shared App Group container** (12-editions.md ▸ Distribution and ▸ Both
|
||||
/// editions installed, ruled 2026-07-29; 02-architecture.md § Per-board app state).
|
||||
///
|
||||
|
||||
@@ -371,7 +371,7 @@ struct BoardAnnouncerSpeechTests {
|
||||
#expect(BoardAnnouncer.speech(for: facts) == "Pulled 3 commits")
|
||||
}
|
||||
|
||||
/// Every base-edition bracket today. The seam exists; pro-m1 supplies the phrases.
|
||||
/// Every free-tier bracket today. The seam exists; pro-m1 supplies the phrases.
|
||||
@Test("A bracket with no phrase to say stays silent rather than falling back to the digest")
|
||||
func bracketWithoutAPhrase() {
|
||||
var facts = BoardAnnouncer.ReloadFacts()
|
||||
@@ -733,7 +733,7 @@ struct BoardAnnouncerStoreTests {
|
||||
#expect(log.lines == ["Card 'Fix login' was deleted externally"])
|
||||
}
|
||||
|
||||
/// The seam pro-m1 fills. No base-edition operation passes a phrase today, so this is the one
|
||||
/// The seam pro-m1 fills. No free-tier operation passes a phrase today, so this is the one
|
||||
/// place the completion path is exercised end to end — including that the phrase is *consumed*
|
||||
/// rather than left armed for whatever reload comes next.
|
||||
@Test("A bracketed operation announces at completion, once, and never again")
|
||||
@@ -755,7 +755,7 @@ struct BoardAnnouncerStoreTests {
|
||||
#expect(log.lines == ["Board changed: 1 card edited"], "the phrase was consumed, not carried forward")
|
||||
}
|
||||
|
||||
@Test("A bracket with no phrase to say stays quiet — every base-edition bracket today")
|
||||
@Test("A bracket with no phrase to say stays quiet — every free-tier bracket today")
|
||||
func silentBracket() async throws {
|
||||
let fixture = try makeBoard()
|
||||
defer { fixture.tearDown() }
|
||||
|
||||
@@ -2,8 +2,9 @@ import Foundation
|
||||
import Testing
|
||||
@testable import Kanban
|
||||
|
||||
/// **The board popover's git-note detection** (12-editions.md § Base and `.git`, ruled 2026-07-27):
|
||||
/// base's whole git story is one line, shown only on a board that carries an inert `.git`. The seam
|
||||
/// **The board popover's git-note detection** (12-editions.md ▸ The free tier and `.git`, ruled
|
||||
/// 2026-07-27): the free tier's whole git story is one line, shown only on a board that carries an
|
||||
/// inert `.git`. The seam
|
||||
/// that decides *whether* to show it — `BoardGitNote.hasGitDirectory(at:)` — is a pure, read-only
|
||||
/// `FileManager` check, pinned here against real bytes on disk. Everything else about the note (its
|
||||
/// wording, its placement in `BoardInfoView`) is SwiftUI rendering and deliberately untested.
|
||||
|
||||
@@ -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"))
|
||||
|
||||
Reference in New Issue
Block a user