The mobile app's first runtime coverage, and its first runtime, full stop: the suite launches the real app over a scratch directory seeded from rich-board.kanban via LANEWORK_LOCAL_ROOT, walks boards to lanes to cards to the card editor, retitles a card and polls the frontmatter on disk until the write lands, then taps through the Settings About section to see the changelog and license actually render. No iCloud account, no metadata query — the DEBUG override is the whole harness. Claude-Session: https://claude.ai/code/session_01SR4XGjmBE16ZUYWpfFHXwY
358 lines
17 KiB
YAML
358 lines
17 KiB
YAML
name: Kanban
|
|
options:
|
|
bundleIdPrefix: dev.rzen.indie
|
|
deploymentTarget:
|
|
macOS: "26.0"
|
|
iOS: "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
|
|
# Local backup/restore for the phone app's Settings tab (indie-backup skill). `KanbanMobile`
|
|
# only — see that target's dependencies below; the Mac app has no Settings surface for this
|
|
# yet. Pinned to `from: "2.0.0"`, the tag the persistence-agnostic API (a single `backupRoot`
|
|
# plus a rebuild-cache-on-restore callback — here, `BoardIndexStore.refresh()`, no database in
|
|
# the loop) shipped under; 1.0.x resolves to the older iCloud-required, CloudKit-coupled
|
|
# surface this app doesn't want.
|
|
IndieBackup:
|
|
url: https://git.rzen.dev/rzen/indie-backup.git
|
|
from: "2.0.0"
|
|
|
|
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: - Lanework for iPhone
|
|
#
|
|
# **The mobile app is a second product, not a second edition** (distinct bundle id, ruled
|
|
# 2026-08-07): `dev.rzen.indie.KanbanMobile`, its own App Store record, iPhone-only for the MVP
|
|
# (iPad joins by widening TARGETED_DEVICE_FAMILY, not by a new target). It compiles
|
|
# `Kanban/Storage` **verbatim — the same source files, not a copy and not a package** — so the
|
|
# two apps cannot drift on the format contract: a storage change that breaks the phone breaks
|
|
# this project's build, in this repo, on the day it's made.
|
|
#
|
|
# What it deliberately does NOT compile: LiveStore (FSEvents — macOS-only machinery; the phone's
|
|
# change signal is NSMetadataQuery over the ubiquity container), Git/History (no git support in
|
|
# the mobile MVP), Tier, and all of `Kanban/UI`. The mobile app's own sources live under
|
|
# `KanbanMobile/`.
|
|
#
|
|
# Boards live in the iCloud Drive container `iCloud.dev.rzen.indie.Kanban` — named after the
|
|
# *Mac* app's bundle id because container ids are not bundle ids, and that name is the one the
|
|
# Mac app can adopt later without migrating anyone's data (see KanbanMobile.entitlements).
|
|
|
|
KanbanMobile:
|
|
type: application
|
|
platform: iOS
|
|
sources:
|
|
- path: KanbanMobile
|
|
excludes:
|
|
- CHANGELOG.md
|
|
# The Settings tab's About section (indie-about skill): the shared ISC license verbatim from
|
|
# the repo root, and a changelog of the phone app's own — it is a separate product with its
|
|
# own 1.0, so the Mac app's CHANGELOG.md would narrate someone else's releases. Both ride in
|
|
# Apple's inline-Markdown subset (bold headings, one physical line per paragraph).
|
|
- path: KanbanMobile/CHANGELOG.md
|
|
type: file
|
|
buildPhase: resources
|
|
- path: LICENSE.md
|
|
type: file
|
|
buildPhase: resources
|
|
# The shared storage engine — frontmatter, loader, writer, ranks, integrity. Pure
|
|
# Foundation + Yams + swift-markdown; no AppKit anywhere in the directory, which is what
|
|
# makes this line possible. One exclusion: AgentGuide reaches up into LiveStore's
|
|
# EchoLedger (its bracketed-write seam), and the AGENTS.md guide it writes is a Mac
|
|
# feature — agents work where the Mac app runs.
|
|
- path: Kanban/Storage
|
|
excludes:
|
|
- AgentGuide.swift
|
|
# The writer layer's one upward dependency: BoardWriter stamps every write into the
|
|
# task-local receipt ledger so a store can tell its own echoes from foreign changes. The
|
|
# recording side is pure CryptoKit + Foundation + Synchronization and compiles here; the
|
|
# ledger's *consumer* surfaces — verdicts (BoardDiff), harvest (HarvestedReceipt), comment
|
|
# retirement (CommentPath) — are the Mac watcher/committer's vocabulary and sit behind
|
|
# `#if os(macOS)` gates inside the file. On the phone the "watcher" is the metadata-query
|
|
# observer; when it grows a verdict surface, those gates are the seam it lands behind.
|
|
- path: Kanban/LiveStore/EchoLedger.swift
|
|
dependencies:
|
|
- package: Yams
|
|
# BodyMarkup (Kanban/Storage) imports Markdown, so the dependency rides along even though
|
|
# the MVP has no preview surface yet.
|
|
- package: swift-markdown
|
|
product: Markdown
|
|
- package: IndieBackup
|
|
# The Settings tab's About section — same package, same pin as the Mac target above.
|
|
- package: IndieAbout
|
|
# Same stamping as the Mac target: CFBundleVersion from git commit count, BuildDate/BuildHash
|
|
# into the product's Info.plist, so the About section can show "Built YYYY-MM-DD".
|
|
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:
|
|
PRODUCT_BUNDLE_IDENTIFIER: dev.rzen.indie.KanbanMobile
|
|
MARKETING_VERSION: "1.0"
|
|
INFOPLIST_FILE: KanbanMobile/Info.plist
|
|
CODE_SIGN_ENTITLEMENTS: KanbanMobile/KanbanMobile.entitlements
|
|
GENERATE_INFOPLIST_FILE: false
|
|
SWIFT_STRICT_CONCURRENCY: complete
|
|
# iPhone only. The iPad milestone widens this to "1,2" rather than adding a target.
|
|
TARGETED_DEVICE_FAMILY: "1"
|
|
|
|
# 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
|
|
|
|
# MARK: - iPhone UI tests
|
|
#
|
|
# The mobile app's first runtime coverage: launches the real app against a plain directory
|
|
# (`LANEWORK_LOCAL_ROOT`, `CloudHome`'s DEBUG override) seeded from a copy of
|
|
# `Fixtures/Valid/rich-board.kanban`, so these tests need no iCloud account and touch nothing but
|
|
# a scratch directory the test itself creates and owns. `Fixtures/` rides in as a folder reference
|
|
# for the same reason `KanbanTests`' copy does — a fixture is a board, and a board is directories
|
|
# on disk.
|
|
|
|
KanbanMobileUITests:
|
|
type: bundle.ui-testing
|
|
platform: iOS
|
|
sources:
|
|
- KanbanMobileUITests
|
|
- path: Fixtures
|
|
type: folder
|
|
buildPhase: resources
|
|
dependencies:
|
|
- target: KanbanMobile
|
|
settings:
|
|
base:
|
|
PRODUCT_BUNDLE_IDENTIFIER: dev.rzen.indie.KanbanMobileUITests
|
|
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
|
|
|
|
# The phone app's own scheme — otherwise auto-generated, declared here only because it needs a
|
|
# test action pointed at `KanbanMobileUITests` rather than xcodegen's per-target default.
|
|
KanbanMobile:
|
|
build:
|
|
targets:
|
|
KanbanMobile: all
|
|
test:
|
|
config: Debug
|
|
gatherCoverageData: false
|
|
targets:
|
|
- KanbanMobileUITests
|