Phase 3 of the one-app pivot (DESIGN 12 ▸ The entitlement / Distribution, ruled 2026-07-30; card c3a3ddd5). New Kanban/Tier/: Tier (.free/.pro — deliberately no .lapsed case; unsubscribed and lapsed are one state) and the pure decision Tier.resolve(from:now:) over SubscriptionFacts (expiration + willAutoRenew), unit-tested through all five named states: free, active, lapsed, offline-grace, never-online. The facts are a persisted cache (standard defaults), not a live view: StoreKit ages an expired subscription out of currentEntitlements locally, so an offline device and a real lapse are indistinguishable from that property alone — the cache holds the last answer, empty entitlements read as silence, and holds end only on a definitive answer (revocation, or the subscription-group status read Settings performs). That is 12's offline-grace trade, resolved toward the paying user. ProEntitlement is the local adapter (currentEntitlements + Transaction.updates, started from launch, never from a test host); ProStorefront holds everything networked (product load, purchase, AppStore.sync) and only the Settings section ever constructs one — the split is the enforcement of "never network on the open path". beginSession reads the tier once at composition; BoardSession.tier is a let with no path back in, so a lapse never rebinds an open session. makeHistoryProvider now takes the tier; both tiers bind the native stack until pro-m1 builds the git provider — the seam's consumer is named, not invented early. Settings gains the Pro section (subscribe with localized price, manage, restore; a quiet unreachable line, no indefinite spinner) — the third of the exactly-three Pro mentions; the About line gains its "…in Settings" pointer now that there is a Settings to point at. A successful purchase or restore offers once to reopen open boards (close + reopen through the ordinary paths). Configuration.storekit wired into the scheme's run action for ASC-free exercise; RELEASE.md gains the pro-m1 store-side steps and the rule that the product must not be configured before then. 1901 tests in 319 suites green. Claude-Session: https://claude.ai/code/session_01SR4XGjmBE16ZUYWpfFHXwY
8.0 KiB
Distribution and release
The channel decision (recorded 2026-07-29)
Lanework ships on the Mac App Store, as version 2.0 of the existing dev.rzen.indie.Kanban App Store record — the 1.x app already listed there under the name Lanework. This was not an open question by the time this document was written; three prior rulings settle it:
- The one-app collapse (DESIGN/12-editions.md, re-ruled 2026-07-30, superseding the 2026-07-27 two-app split) puts the whole family on one record:
dev.rzen.indie.Kanban, the id 1.x already ships under, so 2.0 is simply its update. Lanework Pro arrives inside it as a subscription after pro-m1/pro-m2, never as a second record. - The marketing version has been 2.0 since m3 — the successor numbering of the shipped 1.x.
- The portfolio's whole release tooling (appstore-publish skill,
asc-*.swiftscripts, the account's API key) is App-Store-shaped, and the 1.x pipeline for this very record already exists and works.
Notarized direct distribution is deliberately not pursued for 2.0: a second channel means a second update path and a second set of listing assets for no current audience, and nothing in the app requires escaping the App Store's sandbox rules (the app is already sandboxed with user-selected file access, and its network-client entitlement stays dormant until Pro's remotes use it). Revisit only if App Review rejects something architectural.
The pipeline
Carried over from the 1.x release (same record, same scheme names — the pathfinder repo's pipeline, now living here):
scripts/release.sh— XcodeGen regenerate,xcodebuild archive(Release, generic/platform=macOS),-exportArchivewithscripts/ExportOptions-macOS.plist(destination=upload— straight to App Store Connect). Build number = git commit count, stamped by the existing post-build phase..env.release(gitignored;.env.release.examplecommitted) — account-level App Store Connect API credentials.scripts/metadata/— the listing source of truth (name, subtitle, description, keywords, what's-new, URLs, categories, pricing, availability, review notes). Pushed with the canonical scripts, always with--platform macos:
set -a; source .env.release; set +a; swift ../indie-skills/skills/appstore-publish/scripts/asc-metadata.swift --bundle dev.rzen.indie.Kanban --version 2.0 --platform macos --dry-run
- Screenshots:
scripts/metadata/screenshots/en-US/APP_DESKTOP/— needs fresh 2.0 captures (see checklist).
First-release (2.0) checklist
Run top to bottom; nothing below "PUSH POINT" may run without a deliberate decision — every step after it writes to the live App Store record of a shipping 1.x app.
- All milestone work committed and pushed (build number = commit count).
- Unit suite green:
xcodebuild test -project Kanban.xcodeproj -scheme Kanban -destination 'platform=macOS,arch=arm64' -only-testing:KanbanTests. - UI test pass on a real display (KanbanUITests: accessibility audits + end-to-end golden flows), plus the manual checklists (
KanbanUITests/AccessibilityVerification.md, toolbar/undo/trash/drag-preview lists). - CHANGELOG.md current (it is the in-app release notes) — What's New (
scripts/metadata/version/en-US/whats_new.txt) tells the same story. - Screenshots: capture the 2.0 board on a real display (APP_DESKTOP sizes come straight from screenshots of the running app; numeric filename prefixes set the store order).
- Archive dry run:
scripts/release.shat least through the archive step completes locally. - — PUSH POINT (everything below touches the live record) —
scripts/release.sh— archive + upload the build.- Metadata dry run, then push (
asc-metadata.swift --platform macos, commands above). - Verify pricing (currently free, carried from 1.x) and availability (ALL territories) still match intent — Pro will be the paid tier later.
- Stage:
asc-submit.swift --bundle dev.rzen.indie.Kanban --version 2.0 --platform macos(waits for build processing, attaches, stages the review submission — does not submit). - Review the staged listing at appstoreconnect.apple.com (age rating, privacy labels, and content rights carry over from 1.x — the stage step's errors will name anything missing).
- Submit: the same command with
--submit.
Lanework Pro (the subscription)
Pro is a subscription inside this app, not a second record (DESIGN/12-editions.md ▸ Distribution, re-ruled 2026-07-30): the two-app split that once wanted its own bundle id, metadata folder and scheme here is retired. Nothing in the pipeline above changes for it — same scheme, same archive, same upload. The added work is App Store Connect configuration and review materials, and it is all below.
The app side is built (the entitlement, the tier seam, the Settings Pro section, the local Configuration.storekit). The store side is not and must not be until pro-m1 ships something to sell: a live subscription product against a build whose Pro tier binds the same native undo stack the free tier does would be selling nothing. So every step here is future work for the pro-m1 release, not for 2.0.
What exists in the repo today
Kanban/Tier/ProProducts.swift— the identifiers, mirrored from App Store Connect: productdev.rzen.indie.kanban.pro.monthly, group reference nameLanework Pro. The group's numeric id is read off the loaded product at runtime, never hardcoded.Configuration.storekit(repo root) — the same group and product as an Xcode-local StoreKit configuration, attached to theKanbanscheme's run action (project.yml▸schemes.Kanban.run.storeKitConfiguration). It makes the subscribe/manage/restore flow exercisable with no ASC product and no sandbox account.buildPhase: none— it never ships.- Entitlements: no in-app-purchase key is needed. In-app purchase is an App Store Connect fact about the record, not a sandbox capability; the app calls StoreKit with the entitlements it already has.
Checklist — pro-m1 release (not 2.0)
Steps 1–4 are App Store Connect work done by hand at appstoreconnect.apple.com; there is no script for them and no API step in this repo's tooling. They are all below the PUSH POINT in spirit — each one touches the live record of a shipping app.
- Create the subscription group on
dev.rzen.indie.Kanban: reference name Lanework Pro. One group — a second would let a user hold two Pro subscriptions at once. - Create the auto-renewable subscription in it: product id
dev.rzen.indie.kanban.pro.monthly, reference name "Lanework Pro Monthly", duration 1 month. The product id must matchProProducts.monthlyexactly and can never be changed or reused once created. - Set the price (choose the tier; all territories, matching the app's ALL-territories availability) and the localized display name and description. The app never spells a price — it renders
product.displayPrice, so the store is the only source. - Subscription review materials, which are separate from the app's: a review screenshot of the Settings Pro section showing the price, Subscribe and Restore Purchases; review notes naming Settings (⌘,) as the path to the purchase surface, that the subscription unlocks git-backed history and sync, and that there is no account or login of any kind.
- Confirm the app's own listing carries the required subscription furniture: a link to the privacy policy and to the terms of use (EULA), both reachable from the listing, plus the auto-renewing terms in the description. Apple rejects auto-renewable subscriptions without them.
- Update
Configuration.storekit'sdisplayPriceto whatever step 3 set, so a development run and the store agree. - Verify in a sandbox account before submitting: subscribe, confirm the Settings section flips to "Subscribed — renews …", confirm the reopen offer appears with boards open, then Manage Subscription and Restore Purchases.
- Then run the ordinary release checklist above for the pro-m1 version. The subscription is attached to that version's review submission.