Files
lanework/project.yml
T
rzen c5edcd8528 Run the accessibility verification pass
The automated half: AccessibilityAuditTests runs performAccessibilityAudit
over all eight surfaces DESIGN/10 names — board with trash hidden and
shown, card window in Preview/Edit/raw source, welcome, template chooser,
board popover. One audit per test, .all audit types, no issue handler —
nothing waived; a future false-positive excusal must match one element on
one surface with its reason beside it. Navigation is menu-bar titles and
the arrow grammar; no accessibility identifiers added to production code.

The suite launches with --ui-test-fixture-board: the sandbox forbids
handing the app a temp-folder path (no bookmark behind it), so the flag
carries no payload and the app builds a known board inside its own
container through the ordinary BoardWriter door — three lanes, six cards,
a rich Markdown body with attachment, one card already in .trash/ — with
the registry redirected to the same scratch directory so audit runs never
pollute real recents. LaunchPlan replaces the restore Bool (welcome /
restoreBoards / uiTestFixture, fixture wins outright), decided once in
KanbanApp.init and dispatched by RestoreBootstrapView; pure and pinned by
UITestLaunchTests, and the fixture itself is materialized and read back
through BoardLoader in units — the only proof available headlessly.

The manual half: KanbanUITests/AccessibilityVerification.md is the one
document — the audit suite at the top (it needs a real display and
Accessibility permission), the per-release VoiceOver smoke script with
expected utterances quoted from AccessibilityPhrases, and the
consolidated m11 checklist from all four implementation cards.

1588 unit tests green, UI target compiles, both schemes build. The audit
run and smoke script await a real display — the manual pass is the
user's.

Claude-Session: https://claude.ai/code/session_01SR4XGjmBE16ZUYWpfFHXwY
2026-07-29 10:42:14 -04:00

230 lines
8.5 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
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
dependencies: &appDependencies
- package: Yams
- package: swift-markdown
product: Markdown
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/**
# 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
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 manual verification document lives with the tests it belongs to (10-accessibility.md
# ▸ Verification: "A manual VoiceOver smoke script lives with the test plan"), so it is listed
# in the project — visible where the suite is — but with no build phase: it is documentation,
# not a resource the bundle should carry.
- path: KanbanUITests
excludes:
- "**/*.md"
- path: KanbanUITests/AccessibilityVerification.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