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
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
The golden-path XCUITest suite, adapted to current rulings where the
card body had gone stale: trash flows follow the materialized-trash
grammar (no Put Back, restore is drag or cut/paste out), git flows are
pro-m1 scope and skipped, and fixtures extend m11's in-container
--ui-test-fixture-board mechanism (the sandbox forbids the card's
--open-board path handoff) with exact-match variant flags: standard
(the three-lane audit board), large (8 lanes x 40 cards for
masonry/reflow), malformed (BoardWriter-built board with one card's
index.md overwritten to unterminated YAML, opened through the ORDINARY
path so the failure is the loader's own).
EndToEndFlowTests: create card/lane, inline rename, coordinate drag
across lanes, cut/paste, undo/redo of a move, delete-to-trash /
show-trash / restore-by-cut-paste / Empty Trash confirm - all asserting
on lane accessibility labels. FailFastLaunchTests: welcome appears, no
board window ever, a welcome row carries the loader's sentence naming
the file; byte-fidelity of the malformed board pinned unconditionally
in KanbanTests plus an identically-refused relaunch. Performance:
launch metric plus explicit wall-clock gates (30s launch / 5s Show
Trash on 320 cards) since XCTest baselines don't travel. Powerbox
panels (template save panel, Duplicate fallback, Open) are documented
as manual in EndToEndVerification.md, not faked.
The smoke test now launches on the standard fixture (it launched bare
before, opening the developer's real boards); README's everyday test
command scopes to -only-testing:KanbanTests.
Suite compiles on both schemes (build-for-testing verified); flows
await a real display + automation permission to execute - run
instructions in KanbanUITests/EndToEndVerification.md. +8 unit tests;
1669 green both schemes.
Claude-Session: https://claude.ai/code/session_01SR4XGjmBE16ZUYWpfFHXwY
App identity for both editions: the three-lane glyph
(rectangle.split.3x1.fill) over a gentle gradient - teal for base
Lanework, dark graphite for Lanework Pro - rendered as 1024 masters and
packaged down the full macOS ladder into each target's own iconset,
replacing the borrowed pathfinder icon.
The About window arrives via IndieAbout (0.2.x): icon, copyright,
version/build/date read from the Info.plist that update_build_info.sh
already stamps on every build (CFBundleVersion 160 = git commit count,
BuildDate, BuildHash - verified in the built product), the version line
opening the bundled CHANGELOG.md, and the new ISC LICENSE.md one
document link away. Both files are authored to the Apple
inline-Markdown subset (no # headings, single-line paragraphs) and
bundle into both editions.
CHANGELOG.md replaces its placeholder with the real 2.0 first-release
notes - one sentence per user-facing feature.
The About box is also an edition seam: base's EditionAbout carries the
one-line Pro signpost 12-editions.md allots ("Lanework Pro adds
git-backed board history and sync."), and the Pro target compiles its
own signpost-less twin instead of that file - the no-#if file-level
doctrine applied to a string. README gains the identity bullet.
1649 green on both schemes; verify-editions.sh 26/26.
Claude-Session: https://claude.ai/code/session_01SR4XGjmBE16ZUYWpfFHXwY
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
A template is a board folder the ordinary loader reads — no second
schema, no Swift catalog. BoardTemplate became exactly that: a loaded
BoardModel with chooser-facing derivations, the lane-title stub gone.
TemplateEngine instantiates by the copy-remint-restamp walk: .git and
.trash excluded at top level only — both names mean something at a
board root and nowhere else, and .gitignore must survive — every
materialized folder reminted, created/modified stamped fresh (born
today, not forked), modified-by cleared, the template: key carried
inert, the blurb and style inherited, and loose card files normalized
at this import boundary per the paste precedent so a new board never
opens with a notice about a mess its own birth made. Legacy deleted:
keys copy through verbatim to the one migrator — stripping would
resurrect, skipping would destroy. Atomicity is construct-then-clean:
a sibling temp can be sandbox-refused and a cross-volume rename is
just a second copy, so the call removes what it created on every
non-board exit and never touches an occupied destination. The
cancellable per-item walk extracted into BoardTreeCopy serves
Duplicate and instantiation with two parameters — top-level exclusions
and folder-attribute carriage, the only axes they differ on.
Claude-Session: https://claude.ai/code/session_01SR4XGjmBE16ZUYWpfFHXwY
Two app targets from one source tree — no build flags, no #if in
shared code: an edition difference is a file one target compiles and
the other does not. Base keeps everything it had (dev.rzen.indie.Kanban,
minimal entitlements, AppIcon); KanbanPro compiles the same sources
plus the reserved KanbanPro/ root (Git/, Remote/, Auth/ land with
pro-m1 — libgit2 deliberately not added yet), adds network-client and
its keychain group, and hand-writes its Info.plist with the UTI block
verbatim — base exports the type, Pro imports it, one format either
app opens. The unit-test sources compile twice, once per host, with
Pro's module aliased so 56 test files keep @testable import Kanban
unchanged; scheme Kanban stays the muscle-memory command and
LaneworkPro joins it. InertGitTests pins the base posture with bytes
and mtimes — a full editing session over boards carrying realistic
.git trees at root and nested in a card leaves all twelve entries
untouched, and moves and copies carry them verbatim.
scripts/verify-editions.sh proves the rest: 26 checks over signatures,
symbols, entitlements, identity, and the shared UTI, discounting
Xcode's test-host exceptions by name rather than silently.
Claude-Session: https://claude.ai/code/session_01SR4XGjmBE16ZUYWpfFHXwY
The card body's resting state: swift-markdown (pinned 0.8.0, smart
typography off — Preview renders the bytes on disk) parsed into a pure
BodyMarkup model with UTF-8 source offsets, rendered on one hosted
TextKit 1 NSTextView — chosen because find-in-text is NSTextFinder,
checkbox clicks reuse AppKit character hit-testing, links are .link
attributes, and NSTextTable's automatic layout is exactly the
columns-sized-to-contents rule. The GFM subset renders per 05; HTML
stays verbatim code-styled text; relative images resolve against the
card folder while remote URLs are never fetched, drawing a quiet chip
instead. Task checkboxes are live: a click flips exactly one byte
through a fresh-read, refuse-uneditable, stamp, atomic-replace write —
the app's only offset-addressed write, so a moved target refuses as
staleTarget and what the user saw decides the direction, netting one
toggle on a double-click. Empty bodies open in Edit per CardBodyMode's
opening rule, applied once; the Edit surface itself stays an honest
read-only stub until its card. FindCommand prefers the card body's
find over board search when a card window is focused.
Claude-Session: https://claude.ai/code/session_01SR4XGjmBE16ZUYWpfFHXwY
README with a maintained key-features section, CHANGELOG.md stub in the
portfolio's IndieAbout format, and a post-build phase stamping
CFBundleVersion (git commit count), BuildDate, and BuildHash into the
app and dSYM Info.plists on every build.
Claude-Session: https://claude.ai/code/session_018BjQRYBR6jQja3jCRi5S3A
XcodeGen project (codename Kanban, display name Lanework, bundle id
dev.rzen.indie.Kanban): macOS 26+ app target with Swift 6 strict
concurrency, app sandbox with user-selected file access and app-scoped
bookmark entitlements, plus unit-test (Swift Testing) and XCUITest
targets wired into the Kanban scheme.
Claude-Session: https://claude.ai/code/session_018BjQRYBR6jQja3jCRi5S3A