Files
lanework/project.yml
T
rzen 546ed94412 The pasteboard UTType constants become edition twins — Pro stops claiming ownership it doesn't have
Latent since the m7 split: base's Info.plist exports the three family
pasteboard types (cards, lanes, clipboard) and Pro's imports them, but
the shared constants declared all three UTType(exportedAs:) — a claim
of ownership the system checks against the running app's plist. In Pro
that claim is false, and the first touch (the clipboard type, via the
launch sweep) raised a runtime fault that blocked board loading under
the debugger.

EditionAbout's twin-file pattern, applied to an initializer: the
constants move to Kanban/App/EditionTypes.swift (exportedAs) with a
KanbanPro/Edition/EditionTypes.swift twin (importedAs, identifiers
verbatim); project.yml excludes base's copy from Pro. Type identity is
the string, so payloads cross editions unchanged. Verified: Pro
fixture launch opens the board with zero UTI warnings; both suites
green; verify-editions 30/30.

Claude-Session: https://claude.ai/code/session_01SR4XGjmBE16ZUYWpfFHXwY
2026-07-29 21:46:27 -04:00

265 lines
10 KiB
YAML

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 (both editions): 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
settings:
base:
SWIFT_VERSION: "6.0"
MACOSX_DEPLOYMENT_TARGET: "26.0"
DEVELOPMENT_TEAM: ${APPLE_TEAM_ID}
ENABLE_USER_SCRIPT_SANDBOXING: NO
# The edition split (12-editions.md ▸ Targets). Two app targets compiled from one source tree:
# base builds `Kanban/` alone, Pro builds `Kanban/` *plus* `KanbanPro/`. There is no build flag
# and no `#if` in shared code — "why a real split and not feature flags" (12) is answered by the
# file list, so an edition difference is always a file one target compiles and the other does not.
# libgit2 arrives on the Pro target with pro-m1; the target, its entitlements and its source root
# exist now so that milestone lands without re-splitting anything.
#
# YAML anchors (`&name` / `*name`) carry the genuinely identical parts across the two apps and the
# two unit-test bundles. They are resolved by the YAML parser before XcodeGen sees the file, so
# they cost nothing at generate time and cannot drift the way copy-paste does.
targets:
# MARK: - Lanework (base)
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
dependencies: &appDependencies
- package: Yams
- package: swift-markdown
product: Markdown
- package: IndieAbout
postBuildScripts: &updateBuildInfo
- 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:
# Base keeps the current identifier — it is the app that ships first, so nothing
# re-wires (12 ▸ Targets, ruled 2026-07-27).
PRODUCT_BUNDLE_IDENTIFIER: dev.rzen.indie.Kanban
MARKETING_VERSION: "2.0"
INFOPLIST_FILE: Kanban/Info.plist
CODE_SIGN_ENTITLEMENTS: Kanban/Kanban.entitlements
GENERATE_INFOPLIST_FILE: false
SWIFT_STRICT_CONCURRENCY: complete
ASSETCATALOG_COMPILER_APPICON_NAME: AppIcon
# MARK: - Lanework Pro
KanbanPro:
type: application
platform: macOS
sources:
# The shared tree, minus the three files that are base's *identity* rather than its code.
# (XcodeGen already keeps `Info.plist` and `.entitlements` out of build phases; excluding
# them here keeps them out of the Pro target's file list too, so there is exactly one
# plist and one entitlements file visible per app.)
- path: Kanban
excludes:
- Info.plist
- Kanban.entitlements
- Assets.xcassets
- Assets.xcassets/**
# Base's About-box configuration carries the one-line Pro signpost (12-editions.md ▸
# Quiet signposts); Pro compiles its own `KanbanPro/Edition/EditionAbout.swift` twin
# without it — the edition split's file-level doctrine, applied to a string.
- App/EditionAbout.swift
# Base declares the family pasteboard types `UTType(exportedAs:)` to match its
# UTExportedTypeDeclarations; Pro's plist *imports* them (12-editions.md ▸ Targets), so it
# compiles its own `KanbanPro/Edition/EditionTypes.swift` twin using `importedAs` — the
# same doctrine, applied to an initializer.
- App/EditionTypes.swift
# Pro's own source root — reserved now, filled by pro-m1. See KanbanPro/Edition/ProEdition.swift.
- path: KanbanPro
excludes:
- Info.plist
- KanbanPro.entitlements
# The same bundled template store — the inventory is the app's, not an edition's.
- path: Templates
type: folder
buildPhase: resources
# The About documents, same files as base — one license, one changelog, two bundles.
- path: LICENSE.md
type: file
buildPhase: resources
- path: CHANGELOG.md
type: file
buildPhase: resources
dependencies: *appDependencies
postBuildScripts: *updateBuildInfo
settings:
base:
PRODUCT_BUNDLE_IDENTIFIER: dev.rzen.indie.KanbanPro
MARKETING_VERSION: "2.0"
INFOPLIST_FILE: KanbanPro/Info.plist
CODE_SIGN_ENTITLEMENTS: KanbanPro/KanbanPro.entitlements
GENERATE_INFOPLIST_FILE: false
SWIFT_STRICT_CONCURRENCY: complete
ASSETCATALOG_COMPILER_APPICON_NAME: AppIconPro
# Spelled out because the whole shared-test-sources arrangement below hangs on it: the two
# apps must have *different* Swift module names, since both emit `<module>.swiftmodule`
# into the same `Build/Products/<config>/` and equal names would have one app silently
# overwrite the other's testable interface.
PRODUCT_MODULE_NAME: KanbanPro
# MARK: - Unit tests
#
# One suite, run twice — once hosted by each app. Every test in `KanbanTests/` is
# edition-agnostic (it exercises the storage format, the live store and the UI model, none of
# which differ between editions), so duplicating the *sources* would be duplicating the thing
# that is identical; what is worth running twice is the suite against each *binary*, which is
# what these two targets do. The Pro bundle compiles the very same files — it is the same
# `sources` list, by anchor — against `KanbanPro`.
KanbanTests:
type: bundle.unit-test
platform: macOS
sources: &unitTestSources
- 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
KanbanProTests:
type: bundle.unit-test
platform: macOS
sources: *unitTestSources
dependencies:
- target: KanbanPro
settings:
base:
PRODUCT_BUNDLE_IDENTIFIER: dev.rzen.indie.KanbanProTests
GENERATE_INFOPLIST_FILE: true
SWIFT_STRICT_CONCURRENCY: complete
# The shared sources say `@testable import Kanban`, and they should: they are testing the
# app, not an edition. `-module-alias` (Swift 5.7+) resolves that import to the Pro
# module for this bundle only — the supported way to bind one source name to a different
# module, and it keeps the 56 test files edition-blind instead of littering them with
# conditional imports.
OTHER_SWIFT_FLAGS: -module-alias Kanban=KanbanPro
# MARK: - UI tests
#
# Base-only, deliberately: these tests launch the real app and drive its menu bar, which is the
# slowest, most environment-dependent part of the suite, and a second copy would double it to
# re-assert something the Pro unit bundle already proves (it launches the Pro app as its test host
# on every run). The accessibility audits (10-accessibility.md ▸ Verification) are edition-blind —
# every surface they walk is built from the shared tree.
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:
# `Kanban` is unchanged, on purpose: `xcodebuild … -scheme Kanban` is the established command
# for this repo and base is the app it has always meant.
Kanban:
build:
targets:
Kanban: all
run:
config: Debug
test:
config: Debug
gatherCoverageData: false
targets:
- KanbanTests
- KanbanUITests
profile:
config: Release
analyze:
config: Debug
archive:
config: Release
LaneworkPro:
build:
targets:
KanbanPro: all
run:
config: Debug
test:
config: Debug
gatherCoverageData: false
targets:
- KanbanProTests
profile:
config: Release
analyze:
config: Debug
archive:
config: Release