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
+34
View File
@@ -0,0 +1,34 @@
import IndieAbout
/// **The About box's configuration one of the three places the app names Lanework Pro.**
///
/// 12-editions.md Tier naming: "The free tier presents as a complete app, not a demo: Pro is
/// named in exactly three places one line in the About box, the contextual popover line on
/// `.git` boards, and the Settings Pro section where the subscription actually lives. Nothing on
/// the welcome screen, nothing in banners." The popover line is `BoardGitNote`'s; this is the
/// About line, riding as the second line of the copyright text `IndieAbout` renders
/// `copyrightText` as one multi-line `Text`, so the signpost is literally a line in the box, no
/// custom layout.
///
/// **One box for every tier.** The 2026-07-27 split gave Pro a twin of this file without the
/// signpost, on the reasoning that a separate Pro app pointing at itself would be noise; the
/// one-app collapse (12, re-ruled 2026-07-30) makes Pro a subscription, so the line is a pointer
/// to something a subscriber has already taken up rather than to another download true either
/// way, and shown to everyone. It stays deliberately flat: what Pro adds, in one sentence, with
/// no call to action. It gains its "in Settings" when the Settings Pro section it would point at
/// actually exists (the StoreKit phase); until then a direction would be a dead end.
enum AboutBox {
/// The About window's content: version/build/date from the stamped Info.plist
/// (`update_build_info.sh` CFBundleVersion, BuildDate, BuildHash), the version line
/// opening the bundled CHANGELOG.md, and the bundled ISC LICENSE.md as the one document
/// link. Both documents are authored to the Apple inline-Markdown subset see the files'
/// own single-line paragraphs.
static var configuration: AppInfoConfiguration {
AppInfoConfiguration(
copyrightText: "© 2026 rzen\nLanework Pro adds git-backed board history and sync.",
documents: [.license(extension: "md")],
changelogDocument: .changelog()
)
}
}
+6 -6
View File
@@ -209,11 +209,11 @@ public final class AppModel {
/// **The composition root for `HistoryProviding`** (12-editions.md The provider seam): what a
/// board session's undo stack is built by, called once per board as its session begins.
///
/// Base binds the native stack a pair of step stacks over the inverses registered at the
/// Writer boundary (13-native-undo.md, `NativeHistoryProvider`) and that is the default here
/// because it is the *shared* code's implementation: both targets compile it, and Pro runs it
/// too until pro-m1 replaces this closure with the git provider (06-history-undo.md). Nothing in
/// this file is edition-conditional; the edition difference is which closure the root installs.
/// The free tier binds the native stack a pair of step stacks over the inverses registered at
/// the Writer boundary (13-native-undo.md, `NativeHistoryProvider`) and that is the default
/// here because it is the substrate every tier can always fall back to; pro-m1 installs the git
/// provider over this closure when the subscription is active (06-history-undo.md). Nothing in
/// this file is tier-conditional; the tier difference is which closure the root installs.
///
/// It takes the store because that is what a provider is a history *of*: the git provider needs
/// the board root it is a repository at, and the native one's steps are computed from the same
@@ -239,7 +239,7 @@ public final class AppModel {
/// (13-native-undo.md Rules). It lives here for the store's reason exactly: the session is
/// what every window over this board shares, and "undo is board-local".
///
/// Which implementation it is, is the edition's answer and nobody else's
/// Which implementation it is, is the tier's answer and nobody else's
/// (12-editions.md The provider seam) see `AppModel.makeHistoryProvider`.
public let history: any HistoryProviding
+2 -2
View File
@@ -8,8 +8,8 @@ import UniformTypeIdentifiers
// JSON `ClipboardManifest` below (04-interactions.md Clipboard: "the pasteboard carries a JSON
// manifest + plain text"). Declared in `Info.plist` beside the two drag types, for the same reason
// those are: a payload nobody has declared is a payload the system will not carry. The constant
// itself lives in `EditionTypes.swift` base exports the type, Pro imports it, and each edition's
// twin uses the initializer its own plist can honor.
// itself lives with its two siblings in `PasteboardTypes.swift`, exported once by the app that
// owns it.
// MARK: - The manifest
+5
View File
@@ -19,6 +19,11 @@ import os
/// missing or unreadable **refuses whole and writes nothing** (04-interactions.md Clipboard,
/// re-ruled 2026-07-29 Finder's invariant: an item arrives whole or not at all).
///
/// ** one-app collapse phase 2**: the paragraph below describes a sharing arrangement that stops
/// existing when the App Group does (12-editions.md App-side state, re-ruled 2026-07-30) the
/// staging store moves to the ordinary sandbox container and the sibling it tolerates is only ever
/// the developer's own second copy. The tolerance itself is worth keeping either way.
///
/// **The store is shared by every installed edition** (12-editions.md Both editions installed, ruled
/// 2026-07-29): the group container is one container, so C in base pastes full-fidelity in Pro. The
/// lifecycle below is unchanged by that both editions read the same machine-wide pasteboard, so both
-31
View File
@@ -1,31 +0,0 @@
import IndieAbout
/// **Base Lanework's About-box configuration one of the two places base names Lanework Pro.**
///
/// 12-editions.md Quiet signposts: "Base presents as a complete app, not a demo: Pro is named
/// in exactly two places one line in the About box, and the contextual popover line on `.git`
/// boards. Nothing on the welcome screen, nothing in banners." The popover line is
/// `BoardGitNote`'s; this is the About line, riding as the second line of the copyright text
/// `IndieAbout` renders `copyrightText` as one multi-line `Text`, so the signpost is literally a
/// line in the box, no custom layout.
///
/// **The Pro target does not compile this file** (project.yml excludes it and compiles
/// `KanbanPro/Edition/EditionAbout.swift` instead): Pro's About has no signpost the app it
/// would point to is the one showing the box and falls back to the Info.plist copyright. The
/// difference is a file one target builds and the other does not, per the edition split's
/// no-`#if` doctrine (12-editions.md Targets).
enum EditionAbout {
/// The About window's content: version/build/date from the stamped Info.plist
/// (`update_build_info.sh` CFBundleVersion, BuildDate, BuildHash), the version line
/// opening the bundled CHANGELOG.md, and the bundled ISC LICENSE.md as the one document
/// link. Both documents are authored to the Apple inline-Markdown subset see the files'
/// own single-line paragraphs.
static var configuration: AppInfoConfiguration {
AppInfoConfiguration(
copyrightText: "© 2026 rzen\nLanework Pro adds git-backed board history and sync.",
documents: [.license(extension: "md")],
changelogDocument: .changelog()
)
}
}
-26
View File
@@ -1,26 +0,0 @@
import Foundation
import UniformTypeIdentifiers
// MARK: - The family pasteboard types, as base declares them
/// **Base's half of the edition UTI posture** (12-editions.md Targets: base *exports* the family
/// types, Pro *imports* them, declarations verbatim identical). `UTType(exportedAs:)` is a claim of
/// ownership the system checks against the running app's `Info.plist` in Pro, whose plist imports
/// these, that claim is a runtime fault. So the three constants live in an edition twin rather than
/// beside their payloads: Pro compiles `KanbanPro/Edition/EditionTypes.swift`, which declares the
/// same names with `UTType(importedAs:)` the edition split's file-level doctrine, applied to an
/// initializer.
///
/// What each type *is* stays documented at its payload `DragPayload` for cards and lanes,
/// `ClipboardManifest` for the clipboard type.
extension UTType {
/// A drag carrying board **cards** see `DragPayload`.
static let laneworkCards = UTType(exportedAs: "dev.rzen.indie.kanban.cards")
/// A drag carrying board **lanes** see `DragPayload`.
static let laneworkLanes = UTType(exportedAs: "dev.rzen.indie.kanban.lanes")
/// A Lanework copy's pasteboard manifest see `ClipboardManifest`.
static let laneworkClipboard = UTType(exportedAs: "dev.rzen.indie.kanban.clipboard")
}
+24
View File
@@ -0,0 +1,24 @@
import Foundation
import UniformTypeIdentifiers
// MARK: - The family pasteboard types
/// **The three drag and clipboard types the app owns**, declared once with `UTType(exportedAs:)` to
/// match the `UTExportedTypeDeclarations` in `Kanban/Info.plist`. `exportedAs` is a claim of
/// ownership the system checks against the running app's plist, and one app making it is the whole
/// story there is no importing sibling to keep in step (12-editions.md Distribution, re-ruled
/// 2026-07-30: the types are "declared and exported once, by the one app no ownership twins").
///
/// What each type *is* stays documented at its payload `DragPayload` for cards and lanes,
/// `ClipboardManifest` for the clipboard type.
extension UTType {
/// A drag carrying board **cards** see `DragPayload`.
static let laneworkCards = UTType(exportedAs: "dev.rzen.indie.kanban.cards")
/// A drag carrying board **lanes** see `DragPayload`.
static let laneworkLanes = UTType(exportedAs: "dev.rzen.indie.kanban.lanes")
/// A Lanework copy's pasteboard manifest see `ClipboardManifest`.
static let laneworkClipboard = UTType(exportedAs: "dev.rzen.indie.kanban.clipboard")
}
+5
View File
@@ -110,6 +110,11 @@ enum TemplateEngine {
/// and the clipboard's staging store (09-templates.md Save as Template Storage, re-homed
/// 2026-07-29; 02-architecture.md § Per-board app state, "App-wide state has the same home").
///
/// ** one-app collapse phase 2**: the cross-edition half of this rationale retires with the App
/// Group (12-editions.md App-side state, re-ruled 2026-07-30); the store simply moves to the
/// ordinary sandbox container and keeps every property below, since it never had bookmarks or
/// grants to lose.
///
/// > **templates cross editions**: a template saved in base appears in Pro's chooser, honoring 12's
/// > never-an-empty-home-screen promise (templates are plain board folders no per-edition
/// > semantics, no bookmark grant ceremony; the group container is directly writable by every