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
+3 -3
View File
@@ -13,12 +13,12 @@ import AppKit
/// exactly how the system's Edit Undo row and the toolbar's nil-target pair (`BoardToolbar`) light
/// up, disable and retitle with no code of the app's own.
///
/// The seam, though, must not be an `NSUndoManager`: base's stack is one, Pro's is git
/// The seam, though, must not be an `NSUndoManager`: the free tier's stack is one, Pro's is git
/// (12-editions.md The provider seam), and a protocol that vended one could only ever have had a
/// single implementation. So the substrate stays behind `HistoryProviding` and *this* object is the
/// translation one per board session, over whichever provider that session was composed with. Pro
/// inherits the whole command surface (enablement, dynamic titles, Z, the toolbar pair) by binding
/// its provider and changing nothing here, which is what "a user moving between editions relearns
/// its provider and changing nothing here, which is what "a user subscribing (or lapsing) relearns
/// nothing" (12) has to mean in code.
///
/// ### It deliberately keeps its inherited stack empty
@@ -110,7 +110,7 @@ public final class BoardUndoManager: UndoManager {
// MARK: - BoardUndoRouting
/// Which undo a hosted window answers with, given what holds the keyboard 06-history-undo.md
/// Undo routing, which 12 records as **edition-independent**: "focus decides text-undo vs
/// Undo routing, which 12 records as **tier-independent**: "focus decides text-undo vs
/// board-undo; only the substrate behind board-undo differs".
///
/// ### Most of the rule is AppKit's, and is not here
+1 -1
View File
@@ -8,7 +8,7 @@ import Foundation
///
/// 13-native-undo.md Rules hands the naming straight over: "The 06 vocabulary supplies menu titles
/// ('Undo Move 3 Cards'), via NSUndoManager's dynamic retitling the same naming machinery both
/// editions use." So the verbs here are exactly 06-history-undo.md Commit messages' list *Add /
/// tiers use." So the verbs here are exactly 06-history-undo.md Commit messages' list *Add /
/// Delete / Move / Rename / Edit / Restyle / Resize / Reorder over cards, lanes, and the board*, plus
/// the trash pair's **Restore** and the plural rule is that section's own plural folding ("Delete
/// 12 cards"), which is also 13's coalescing sentence read out loud: "a multi-card move is one step
+13 -13
View File
@@ -80,14 +80,14 @@ public enum HistoryStepOutcome: Equatable, Sendable {
/// holds: move move back ...; rename restore title" and both halves of that write are already
/// in the caller's hands: the before-value *is* the inverse, and the after-value is what the write
/// set (which is also what the staleness predicate compares). A step is therefore that pair, in the
/// design's own terms, and deliberately says nothing about how a stack stores it: base's stack is
/// `NSUndoManager`-backed and Pro's is git (12-editions.md The provider seam), and neither
/// design's own terms, and deliberately says nothing about how a stack stores it: the free tier's
/// stack is `NSUndoManager`-backed and Pro's is git (12-editions.md The provider seam), and neither
/// substrate appears here.
///
/// ### `name` is the 06 vocabulary, unprefixed
///
/// "The 06 vocabulary supplies menu titles ('Undo Move 3 Cards'), via NSUndoManager's dynamic
/// retitling the same naming machinery both editions use" (13). What the step carries is the bare
/// retitling the same naming machinery both tiers use" (13). What the step carries is the bare
/// phrase `"Move Card"`, `"Move 3 Cards"`, `"Rename Lane"` in the vocabulary of
/// 06-history-undo.md Commit messages, plural-folded by the same rule ("one gesture, one undo
/// step a multi-card move is one step with a plural title"). The **"Undo "/"Redo " prefix is
@@ -143,22 +143,22 @@ public struct HistoryStep {
/// "One stack per board, owned by the board session. Not per-window: every window over a board
/// (board window, its card windows) shares the store and shares the stack" (13-native-undo.md
/// Rules). `AppModel.BoardSession` is where that ownership lives, and the composition root binds
/// which implementation it gets: base binds `NativeHistoryProvider` (two step stacks over the
/// inverses registered at the Writer boundary), Pro binds the git provider in pro-m1 (undo as
/// which implementation it gets: the free tier binds `NativeHistoryProvider` (two step stacks over
/// the inverses registered at the Writer boundary), Pro binds the git provider in pro-m1 (undo as
/// forward restore commits over HEAD's first-parent ancestry 06-history-undo.md), Teams inherits
/// Pro's.
///
/// ### What this protocol deliberately does not say
///
/// - **No `NSUndoManager`, anywhere in the signature.** It is base's implementation detail, and a
/// seam that vended one would be a seam only one provider could ever satisfy the opposite of
/// the reason the split exists at all ("base's native undo is the first proof the seam is real",
/// 12). AppKit still needs an `UndoManager` to hand the responder chain; that adapter is
/// `BoardUndoManager`, which sits *over* this protocol rather than inside it.
/// - **No persistence promise.** Base's stack dies with the session (13); Pro's survives relaunch
/// because git does (06). Both are honest implementations of these seven members.
/// - **No `NSUndoManager`, anywhere in the signature.** It is the native provider's implementation
/// detail, and a seam that vended one would be a seam only one provider could ever satisfy the
/// opposite of the reason the seam exists at all ("the free tier's native undo is the first proof
/// the seam is real", 12). AppKit still needs an `UndoManager` to hand the responder chain; that
/// adapter is `BoardUndoManager`, which sits *over* this protocol rather than inside it.
/// - **No persistence promise.** The native stack dies with the session (13); Pro's survives
/// relaunch because git does (06). Both are honest implementations of these seven members.
/// - **No routing.** Which surface Z reaches is focus's answer, not the substrate's
/// (06 Undo routing, edition-independent) `BoardUndoRouting`.
/// (06 Undo routing, tier-independent) `BoardUndoRouting`.
@MainActor
public protocol HistoryProviding: AnyObject {
+1 -1
View File
@@ -2,7 +2,7 @@ import Foundation
// MARK: - NativeHistoryProvider
/// The base edition's undo substrate: one stack per board session (13-native-undo.md).
/// The free tier's undo substrate: one stack per board session (13-native-undo.md).
///
/// ### Two arrays, and why not `NSUndoManager`
///
+16 -8
View File
@@ -8,14 +8,22 @@
<true/>
<key>com.apple.security.files.bookmarks.app-scope</key>
<true/>
<!-- The family App Group (12-editions.md ▸ Distribution, ruled 2026-07-29). Every edition
declares this same group id, and the board registry with its app-wide peers — the
clipboard's staging store, the shared defaults suite — homes in its container from day
one, so a paying upgrader launches Pro onto their own recents rather than an empty
screen. **It must be in base's shipped entitlements**: base ships first, and an edition
that joined the group later would have a migration to do. Security-scoped bookmarks are
the stated exception — they never cross sandboxes, group or not, which is why each
edition holds its own grant slot on the shared record. -->
<!-- Remotes: pull/push and auth (07-sync-collab.md), which only the git provider ever makes.
**Declared now, dormant until Pro ships** — the one-app collapse traded "the free app
provably has no network access" for "no network use until you subscribe", with eyes open
(12-editions.md ▸ The target, re-ruled 2026-07-30). It sits in the shipped entitlements
from the start because a key added later is a new provisioning profile and a new review
surface for what is meant to be a StoreKit-only change. No `keychain-access-groups`
alongside it: a group shares items *between* apps, and there is one app — Pro's
credentials go to the sandbox's own keychain, which needs no key at all. -->
<key>com.apple.security.network.client</key>
<true/>
<!-- The App Group, on borrowed time. 12-editions.md ▸ App-side state (re-ruled 2026-07-30)
removes it wholesale — the board registry and its Application Support peers home in the
ordinary sandbox container once there is one app — but the removal is the *code's* to
make, not this file's: `AppGroup.swift` still resolves its container through this key,
and pulling it first would silently drop the app into the fallback home. It goes with
that code. -->
<key>com.apple.security.application-groups</key>
<array>
<string>group.dev.rzen.indie.Kanban</string>
+3 -3
View File
@@ -182,9 +182,9 @@ struct KanbanApp: App {
// The About window (11-command-nexus.md files About under the app menu's standard
// furniture): icon, version/build/date from the Info.plist that `update_build_info.sh`
// stamped at build time never a hardcoded string with the version line opening the
// bundled changelog. `EditionAbout` is the edition seam: base's carries the one-line Pro
// signpost (12-editions.md Quiet signposts), Pro's twin does not.
IndieAboutCommand(configuration: EditionAbout.configuration)
// bundled changelog. `AboutBox` carries the one-line Pro signpost with it (12-editions.md
// Tier naming) one box, every tier.
IndieAboutCommand(configuration: AboutBox.configuration)
// The File group, in 11-command-nexus.md's own row order: New Card, New Lane, New Board,
// Open, Open Recent , Board Info, Duplicate, Save as Template, Reveal in Finder, Add
+16 -7
View File
@@ -1,6 +1,14 @@
import Foundation
import os
/// ** 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: with one app there is no sibling
/// to share a container *with*, so the registry and its peers home in the ordinary sandbox
/// container, records carry one grant and one open-now flag, and this type goes away with the
/// entitlement. Everything below still describes the shipping code, and the code still works
/// it is simply describing a world that is being dismantled in a later phase, so the two-app
/// reasoning is left standing rather than half-rewritten into a fiction.
///
/// **The family App Group** where every edition's app-side state lives (12-editions.md
/// § Distribution, ruled 2026-07-29; 02-architecture.md § Per-board app state).
///
@@ -44,10 +52,11 @@ public enum AppGroup {
/// case and never a shipped app's.
public static let baseEditionID = "dev.rzen.indie.Kanban"
/// Pro's bundle id (12 Targets, ruled 2026-07-27). Named here as well as in
/// `ProEdition.bundleIdentifier` because *base* has to know it: the popover's awareness line and
/// the grant-slot keying are shared-tree code that must be able to name the other edition
/// without compiling any of it.
/// The bundle id the retired Pro *app* would have carried (12 Targets, ruled 2026-07-27)
/// **one-app collapse phase 2**: no app has ever shipped under it, and nothing will now that Pro
/// is a subscription rather than a second binary (12 Distribution, re-ruled 2026-07-30). It
/// stays only because the awareness line and the grant-slot keying still read it; both go in the
/// same phase, and this constant with them.
public static let proEditionID = "dev.rzen.indie.KanbanPro"
/// Which edition is running the key every per-edition slot on a shared record is stored under.
@@ -128,12 +137,12 @@ public enum AppGroup {
///
/// ### Why the app has to know
///
/// The unit-test host **is the app** (`KanbanTests` and `KanbanProTests` are hosted bundles), so
/// The unit-test host **is the app** (`KanbanTests` is a hosted bundle), so
/// `KanbanApp.init()` runs for real on every test launch and builds an `AppModel` over whatever
/// the defaults resolve to. Every *object* a test constructs takes its storage by injection that
/// is the seam, and it is untouched but the host's own launch has no injection point, and after
/// the 2026-07-29 ruling the thing it would reach for is a container shared with the sibling
/// edition and with the developer's running copy. Its launch sweep would collect real staged
/// the 2026-07-29 ruling the thing it would reach for is a container shared with the developer's
/// own running copy. Its launch sweep would collect real staged
/// clipboard trees; its `refreshRecents()` would resolve, refresh and rewrite real records.
///
/// So the *default* moves for a test host, which is the one place a default can be wrong in a way
+1 -1
View File
@@ -219,7 +219,7 @@ public enum BoardAnnouncer {
/// What that operation wants said when it lands "Pulled 3 commits", "Switched to branch
/// 'redesign'". `nil` for a bracket whose completion is not worth speech, which is every
/// base-edition bracket today (see `BoardStore.performWholesale(announcing:_:)`).
/// free-tier bracket today (see `BoardStore.performWholesale(announcing:_:)`).
public var completion: String?
/// The snapshot comparison **already narrowed to the foreign-classified changes**
+4 -4
View File
@@ -19,11 +19,11 @@ import Foundation
/// ### Why it lives in `LiveStore/` and not beside the announcement
///
/// 10 says announcements "reuse the auto-committer's summarizer" and 06-history-undo.md says the
/// committer synthesizes its commit messages from the same comparison. The committer is Pro-edition
/// committer synthesizes its commit messages from the same comparison. The committer is Pro-tier
/// work (pro-m1) and does not exist yet, so the summarizer is built here first, in the layer both
/// consumers can reach: base compiles `Kanban/`, Pro compiles `Kanban/` *plus* `KanbanPro/`
/// (12-editions.md Targets), so a type in the store layer is available to the committer by
/// construction while a type in `KanbanPro/` would not be available to the announcer. It imports
/// consumers can reach: the store layer is below the history providers, so it is available to the
/// committer by construction, where a type living inside the git provider would not be reachable
/// from the announcer (12-editions.md The provider seam). It imports
/// `Foundation` alone and touches no view, no window, and no `NSAccessibility` the phrasing is
/// `AccessibilityPhrases`' job, and pro-m1's message composer will phrase the very same counts
/// differently without either of them knowing about the other.
+5
View File
@@ -65,6 +65,11 @@ public struct WindowFrame: Codable, Sendable, Equatable {
///
/// ### One record, shared by every edition with two per-edition fields
///
/// ** one-app collapse phase 2**: the two per-edition fields collapse to one grant and one flag
/// when the App Group goes (12-editions.md App-side state, re-ruled 2026-07-30) there is no
/// second sandbox to hold a slot for. The section below describes the shipping code, which still
/// works; it is retired, not wrong.
///
/// The record lives in the family App Group container (`AppGroup`), so base, Pro and later Teams all
/// read and write the same one: an upgrader's recents, frames and settings are simply *there*
/// (12-editions.md Distribution, ruled 2026-07-29). Two fields cannot be common, and both are
+5 -5
View File
@@ -344,7 +344,7 @@ public final class BoardStore: HealHost {
/// Its consumers today are the announcer's digest and the vanishing-focus sentence, both
/// through `land`. Pro's auto-committer (06-history-undo.md) becomes the second one without
/// this line changing which is why the type lives in `LiveStore/` beside `BoardDiff` rather
/// than in `KanbanPro/`.
/// than inside the git provider.
@ObservationIgnored
public let echoes = EchoLedger()
@@ -477,7 +477,7 @@ public final class BoardStore: HealHost {
/// raised lock above the completion), and the phrase must not survive to be spoken by some
/// later, unrelated reload.
///
/// **`nil` on every base-edition bracket today.** Base has no git operations, and the design's
/// **`nil` on every free-tier bracket today.** The free tier has no git operations, and the design's
/// examples ("Pulled 3 commits", "Switched to branch 'redesign'") are pro-m1's; the parameter
/// exists so that milestone supplies phrasing rather than re-plumbing the seam.
@ObservationIgnored
@@ -1111,8 +1111,8 @@ public final class BoardStore: HealHost {
/// - Parameter completion: what to announce when the closing reload lands
/// (10-accessibility.md Live board announcements: "bracketed operations announce once, at
/// completion" "Pulled 3 commits", "Switched to branch 'redesign'"). `nil`, the default, is
/// an operation whose completion is not worth speech, which is **every base-edition bracket
/// today**: base has no git operations, and the two app-initiated writes that do reach disk on
/// an operation whose completion is not worth speech, which is **every free-tier bracket
/// today**: no git operations run there, and the two app-initiated writes that do reach disk on
/// their own the loose-file relocation and the legacy-tombstone migration are ordinary
/// `performWrite` calls that already say what they did on the banner strip. The parameter is
/// the seam pro-m1 fills; see `wholesaleCompletion`.
@@ -3881,7 +3881,7 @@ public final class BoardStore: HealHost {
on: self
) { () throws(BoardWriteError) -> Void in
// One bracket over the displacement *and* the write: two files change, one app-mediated
// reload lands, and (in the Pro edition) one honestly-attributed commit records it.
// reload lands, and (under Pro) one honestly-attributed commit records it.
guard let moved = try AgentGuide.install(atBoardRoot: root) else { return }
// The `CLAUDE.user.md` rescue is the settled, silent ownership rule; a squatter's
// displacement is announced.
+1 -1
View File
@@ -215,7 +215,7 @@ enum AgentGuide {
/// when the decision called for it.
///
/// **Called inside `BoardStore.performWrite`**, so both halves ride one watcher bracket: the
/// rescue and the guide land as a single app-mediated reload, and (in the Pro edition) as a
/// rescue and the guide land as a single app-mediated reload, and (under Pro) as a
/// single honestly-attributed commit rather than a foreign-looking rename followed by an
/// app write (06-history-undo.md Commit messages, "Update agent guide (vN)").
///
+5 -6
View File
@@ -603,15 +603,14 @@ public enum BoardLoader: Sendable {
/// § Fractal layout Rules: "on git boards, the path history already tracks outranks the
/// newcomer (both tracked: the path that entered history first)").
///
/// A seam rather than an implementation because the first rung of that ladder is unbuildable in
/// base: base Lanework links no git machinery at all (12-editions.md; `scripts/verify-editions.sh`
/// scans the base binary to prove it), so the loader consults an injected ranker and falls through
/// to birth date and traversal order when there is none which is every base board, and every Pro
/// board without a repo.
/// A seam rather than an implementation because the first rung of that ladder is unreachable
/// without git: the free tier runs no git machinery at all (12-editions.md The inert posture),
/// so the loader consults an injected ranker and falls through to birth date and traversal order
/// when there is none which is every free-tier board, and every Pro board without a repo.
///
/// Deliberately one closure and no protocol: the loader asks one question "how early did this
/// path enter history" and pro-m1's implementation answers it from `git log --diff-filter=A
/// --follow`-shaped plumbing behind the edition seam. `nil` means "untracked, or no history
/// --follow`-shaped plumbing behind the provider seam. `nil` means "untracked, or no history
/// here", which the rule reads as *outranked by anything tracked*.
///
/// - Parameter rank: keyed by the occurrence's **board-root-relative path**, which is what a
+19 -9
View File
@@ -305,17 +305,20 @@ private struct BoardRenameField: View {
// MARK: - Git
/// The contextual git note **a quiet signpost, not a feature** (12-editions.md § Base and `.git`,
/// settled 2026-07-27). Base has no git integration and never will (that is Pro's), so this is not a
/// grow-in-place slot the way the old `BoardGitSlot` placeholder was: there is nothing here to grow.
/// Base's whole git story is one line, shown only when it is true.
/// The contextual git note **a quiet signpost, not a feature** (12-editions.md The free tier and
/// `.git`, settled 2026-07-27, carried through the one-app collapse). The free tier has no git
/// integration (that is the Pro subscription's), so this is not a grow-in-place slot the way the old
/// `BoardGitSlot` placeholder was: there is nothing here to grow. The free tier's whole git story is
/// one line, shown only when it is true and it is **the one in-context pointer to Pro**, the second
/// of the three places the app names it (12 Tier naming; the other two are `AboutBox` and the
/// Settings Pro section).
///
/// On an ordinary board `BoardInfoView` never instantiates this type at all the section is
/// *absent*, matching the card window's absent History section (12: "absent, no placeholder"). Only
/// a board that carries an inert `.git` (12-editions.md § The inert posture: "any `.git` is inert"
/// base never reads or writes it, whether the board's own or a Pro user's) earns this note, worded
/// exactly as 12 rules: an honest explanation of what the folder is, named exactly where the
/// question arises, never a standing ad for Pro.
/// a board that carries an inert `.git` (12 the inert posture: "any `.git` is inert" the free
/// tier never reads or writes it, whether the board's own or one a lapsed subscription left behind)
/// earns this note, worded exactly as 12 rules: an honest explanation of what the folder is, named
/// exactly where the question arises, never a standing ad for Pro.
///
/// Not `private`: `hasGitDirectory(at:)` is the pure seam `BoardInfoPopoverTests.swift` pins directly
/// (a fixture board with `.git` true, without false), which needs it visible past this file even
@@ -329,7 +332,7 @@ struct BoardGitNote: View {
.fixedSize(horizontal: false, vertical: true)
}
/// Whether `boardRoot` carries a `.git` entry base's entire detection story, and a deliberately
/// Whether `boardRoot` carries a `.git` entry the free tier's entire detection story, and a deliberately
/// small one: nothing in `BoardStore`, `BoardModel`, or `BoardLoader` tracks this as a live fact
/// today, because nothing needs it to be live. `FolderWatcher` filters `.git` out of the folder
/// watch by design (§ .git filtering it exists to ignore git churn), so there is no reload
@@ -344,6 +347,13 @@ struct BoardGitNote: View {
// MARK: - The other edition
/// ** Retired by the one-app collapse one-app collapse phase 2.** There is no other edition to be
/// aware of: 12-editions.md App-side state (re-ruled 2026-07-30) removes the App Group and with it
/// the cross-edition flags this line reads, and 12 Distribution retires the second app outright.
/// This type, `BoardInfoView.otherEditionNote` and `BoardRecord.openNow`'s keying go together in that
/// phase; until then the line is simply never non-`nil` in practice, since no sibling exists to set a
/// flag. Kept functioning, and documented as it was built, rather than half-unwound here.
///
/// **The cross-edition awareness line** "Also open in Lanework Pro" (12-editions.md Both editions
/// installed, ruled 2026-07-29).
///
+2 -2
View File
@@ -38,9 +38,9 @@ extension NSToolbarItem.Identifier {
/// predicate written here it is literally the same validation. Both reach the board window, whose
/// `windowWillReturnUndoManager` hands back the session's `BoardUndoManager`, and both therefore
/// enable exactly when that board has a step to cross and no read-only lock stands
/// (13-native-undo.md Rules). **Every base board has undo**, so there is no edition-shaped
/// (13-native-undo.md Rules). **Every board has undo in every tier**, so there is no tier-shaped
/// disablement to write: 03's parenthetical about boards without undo is 06's *git* substrate, which
/// only Pro binds.
/// only a Pro subscription binds.
///
/// Their labels are the design's one exception to the menu-title rule: `NSUndoManager` rewrites the
/// *menu* titles as the stack changes ("Undo Move Card"), which a toolbar label does not track, so
+1 -2
View File
@@ -9,8 +9,7 @@ import UniformTypeIdentifiers
// and the cross-board lane transfer. Declared in `Info.plist` beside the clipboard type, because a
// system drag session is what crosses window boundaries, shows the copy badge, and gives the
// full-size replica preview (DRAG-REORDER.md § Cross-board sessions). The constants themselves live
// in `EditionTypes.swift` base exports the types, Pro imports them, and each edition's twin uses
// the initializer its own plist can honor.
// with the clipboard type in `PasteboardTypes.swift`, exported once by the app that owns them.
// MARK: - What a drag carries
+1 -1
View File
@@ -46,7 +46,7 @@ import Observation
/// closing and it is deliberately a named call rather than a side effect of `flush()`, because
/// pro-m1's auto-commit coalesces exactly here: every debounced tick inside one session rides its
/// own `performWrite` bracket, and the committer's rule is one commit per *session*, "never per save
/// tick" (06-history-undo.md Rules Auto-commit). On the base edition there is no committer, so
/// tick" (06-history-undo.md Rules Auto-commit). In the free tier there is no committer, so
/// the two calls do the same work today; the seam is what keeps them from having to be pulled apart
/// later.
@MainActor