name: Kanban options: bundleIdPrefix: dev.rzen.indie deploymentTarget: macOS: "26.0" xcodeVersion: "26.0" defaultConfig: Debug packages: Yams: url: https://github.com/jpsim/Yams.git from: 6.0.0 # apple/swift-markdown — the card window's Preview parser (05-card-window.md ▸ Preview). # Pinned to the next *minor* rather than the next major because the package is pre-1.0: a # `from:` range here would silently accept 0.9's breaking changes, which is not what `from:` # means for Yams at 6.x. swift-markdown: url: https://github.com/apple/swift-markdown.git minorVersion: 0.8.0 # The About window: app icon, version/build from the stamped Info.plist, and the bundled # LICENSE/CHANGELOG documents. Pre-1.0 like swift-markdown, same pinning logic. IndieAbout: url: https://git.rzen.dev/rzen/indie-about.git minorVersion: 0.2.2 # **The bundled git client** (06-history-undo.md ▸ Rules: "Bundled libgit2 — no git install # required"): SwiftGitX vendors libgit2 as an in-process library, so every git operation the app # performs runs inside the sandbox with no `Process`, no `/usr/bin/git`, and no sandbox extension. # A board's history therefore works identically in a Release build on a machine that has never had # Xcode or the command-line tools installed. # # `exactVersion` rather than a range, and pinned to the pathfinder's own 0.4.0: the package is # pre-1.0 with an API that moves between minors (the commit and diff surfaces this depends on both # changed shape in 0.3→0.4), so a floating pin would be a resolver deciding when the git provider # stops compiling. SwiftGitX: url: https://github.com/ibrahimcetin/SwiftGitX exactVersion: 0.4.0 # **The C library underneath SwiftGitX, named directly** (06-history-undo.md ▸ Interaction with # external writers: "foreign changes are committed under the pinned synthetic author"). SwiftGitX # 0.4.0's `commit(message:)` takes no signature — its `CommitOptions` leaves `author`/`committer` # null and libgit2 falls back to `git_signature_default`, which reads a config the sandbox cannot # see — so the auto-committer's per-commit authorship is unreachable through the wrapper. It # reaches `git_commit_create` itself (`GitCommitOperation`), which needs the same vendored module # SwiftGitX imports. # # **The same package SwiftGitX already resolves**, pinned to the version SwiftGitX pins exactly # (`.package(url: …/libgit2.git, exact: "1.9.2")`): this adds no second copy of libgit2 to the # binary and no second resolution to argue with — it makes a module the app already links # *importable*. A drift between these two pins is a resolver error at build time, which is the # loudest place for it to be. libgit2: url: https://github.com/ibrahimcetin/libgit2.git exactVersion: 1.9.2 settings: base: SWIFT_VERSION: "6.0" MACOSX_DEPLOYMENT_TARGET: "26.0" DEVELOPMENT_TEAM: ${APPLE_TEAM_ID} ENABLE_USER_SCRIPT_SANDBOXING: NO # **One app target** (12-editions.md ▸ The target, re-ruled 2026-07-30). Lanework ships as a single # Mac App Store app, `dev.rzen.indie.Kanban`; **Lanework Pro is a subscription inside it**, not a # second binary. The 2026-07-27 two-target split — a `KanbanPro` application target, its scheme, its # bundle id, the `-module-alias Kanban=KanbanPro` test bundle and the twin source files that existed # only because two bundles claimed different UTI ownership — retired wholesale with the collapse. # # What that buys this file: no anchors, no per-target exclusion lists, no second copy of the # resources. There is one product, one Info.plist, one entitlements file, one Swift module, and one # `.kanban` UTI **exported** (never imported) by the app that owns it. # # libgit2 arrived here with pro-m1, as SwiftGitX above — a dependency of the one target, dormant # behind the subscription gate rather than the contents of a second download. It links into the free # app and never runs there: detection itself is tier-gated (`HistoryStore.compose`), so a free-tier # session opens without so much as a `fileExists` under `.git` (12-editions.md ▸ The free tier and # `.git`, and `InertGitTests`). targets: # MARK: - Lanework Kanban: type: application platform: macOS sources: - Kanban # The bundled template store (09-templates.md ▸ Definition format): real board folders, # copied into `Contents/Resources/Templates/` verbatim as a folder reference — the same # arrangement `Fixtures/` uses below, and for the same reason. A template is a board, so it # must reach the bundle as directories on disk, not as flattened resource files. - path: Templates type: folder buildPhase: resources # The About window's documents (LICENSE, CHANGELOG) — bundled so IndieAbout can render # them in-app. Apple-inline-Markdown subset only: no `#` headings, no hard-wrapped # paragraphs (every newline renders literally). - path: LICENSE.md type: file buildPhase: resources - path: CHANGELOG.md type: file buildPhase: resources # The local StoreKit configuration (12-editions.md ▸ Distribution) — the subscription group # "Lanework Pro" and its monthly product, mirroring what App Store Connect declares, so the # Settings Pro section's subscribe/manage/restore flow is exercisable in Xcode without an ASC # product existing yet. Attached to the scheme's run action below. # # **`buildPhase: none`, deliberately.** It is development configuration, not app content: the # scheme reads it, the shipped bundle must never carry it. Listed in the project anyway, for # the same reason `KanbanUITests`' verification documents are — visible where it belongs, # built into nothing. - path: Configuration.storekit type: file buildPhase: none dependencies: - package: Yams - package: swift-markdown product: Markdown - package: IndieAbout - package: SwiftGitX - package: libgit2 postBuildScripts: - script: '"${SRCROOT}/../indie-skills/skills/app-versioning/scripts/update_build_info.sh"' name: Update Build Info shell: /bin/sh basedOnDependencyAnalysis: false inputFiles: - $(TARGET_BUILD_DIR)/$(INFOPLIST_PATH) - $(DWARF_DSYM_FOLDER_PATH)/$(DWARF_DSYM_FILE_NAME)/Contents/Info.plist settings: base: # The identifier 1.x shipped under, unchanged: 2.0 updates that App Store record rather # than opening a second one (12-editions.md ▸ Distribution, ruled 2026-07-30). PRODUCT_BUNDLE_IDENTIFIER: dev.rzen.indie.Kanban MARKETING_VERSION: "2.0" INFOPLIST_FILE: Kanban/Info.plist # `Kanban/Kanban.entitlements` — sandbox, user-selected files, app-scope bookmarks, and # `com.apple.security.network.client`, which is **declared now and dormant until Pro # ships**: the one binary carries the key, and nothing exercises it until the git # provider's remotes do under an active subscription (12 ▸ The target). No App Group and # no keychain access group — groups exist to share *between* apps, and there is one app, # so app-side state homes in the ordinary sandbox container (`AppStateHome`). CODE_SIGN_ENTITLEMENTS: Kanban/Kanban.entitlements GENERATE_INFOPLIST_FILE: false SWIFT_STRICT_CONCURRENCY: complete ASSETCATALOG_COMPILER_APPICON_NAME: AppIcon # MARK: - Unit tests # # The suite, hosted by the app: every test in `KanbanTests/` exercises the storage format, the # live store or the UI model through `@testable import Kanban`, so it needs the app's module and # its bundle around it. `Fixtures/` rides in as a folder reference for the same reason the # templates do — a fixture is a board, and a board is directories on disk. KanbanTests: type: bundle.unit-test platform: macOS sources: - KanbanTests - path: Fixtures type: folder buildPhase: resources dependencies: - target: Kanban settings: base: PRODUCT_BUNDLE_IDENTIFIER: dev.rzen.indie.KanbanTests GENERATE_INFOPLIST_FILE: true SWIFT_STRICT_CONCURRENCY: complete # MARK: - UI tests # # These launch the real app and drive its menu bar — the slowest, most environment-dependent part # of the suite. The accessibility audits (10-accessibility.md ▸ Verification) walk every surface # the design names. KanbanUITests: type: bundle.ui-testing platform: macOS sources: # The verification documents live with the tests they belong to (10-accessibility.md # ▸ Verification: "A manual VoiceOver smoke script lives with the test plan"), so they are # listed in the project — visible where the suite is — but with no build phase: they are # documentation, not resources the bundle should carry. `EndToEndVerification.md` is the same # arrangement for the golden-flow, fail-fast and performance suites: the run command, the # prerequisites, and the flows that stayed manual. - path: KanbanUITests excludes: - "**/*.md" - path: KanbanUITests/AccessibilityVerification.md buildPhase: none - path: KanbanUITests/EndToEndVerification.md buildPhase: none dependencies: - target: Kanban settings: base: PRODUCT_BUNDLE_IDENTIFIER: dev.rzen.indie.KanbanUITests GENERATE_INFOPLIST_FILE: true SWIFT_STRICT_CONCURRENCY: complete schemes: # `xcodebuild … -scheme Kanban` is the established command for this repo, and it now means the # only app there is. Kanban: build: targets: Kanban: all run: config: Debug # StoreKit Testing in Xcode (12-editions.md ▸ Distribution): a ⌘R run resolves # `dev.rzen.indie.kanban.pro.monthly` against the local `Configuration.storekit` instead of # against the App Store, so the Settings Pro section shows a price and its purchase flow # completes on a machine with no App Store Connect product and no sandbox account. # # It changes nothing about a build or an archive — it is a *run action* setting, so `xcodebuild # build`, `xcodebuild test` and `scripts/release.sh` are untouched, and a shipped app resolves # the real product or reports that it cannot reach the App Store. storeKitConfiguration: Configuration.storekit test: config: Debug gatherCoverageData: false targets: - KanbanTests - KanbanUITests profile: config: Release analyze: config: Debug archive: config: Release