Lanework Pro is a subscription — the tier seam, StoreKit 2, and Settings

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
This commit is contained in:
2026-07-30 18:14:58 -04:00
parent 2c6b8fe63a
commit 3b19883593
15 changed files with 1459 additions and 15 deletions
+23 -2
View File
@@ -42,6 +42,27 @@ Run top to bottom; nothing below "PUSH POINT" may run without a deliberate decis
12. [ ] 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).
13. [ ] Submit: the same command with `--submit`.
## Lanework Pro (later)
## 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. It ships after pro-m1 (git history) and pro-m2 (remote sync) as an auto-renewable subscription configured against `dev.rzen.indie.Kanban` in App Store Connect. Nothing in this pipeline changes for it — same scheme, same archive, same upload; the subscription products and their review materials are the added work, and this section gets its steps when that phase is designed.
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: product `dev.rzen.indie.kanban.pro.monthly`, group reference name `Lanework 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 the `Kanban` scheme'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 14 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.
1. [ ] 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.
2. [ ] 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 match `ProProducts.monthly` exactly and can never be changed or reused once created.
3. [ ] 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.
4. [ ] 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.
5. [ ] 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.
6. [ ] Update `Configuration.storekit`'s `displayPrice` to whatever step 3 set, so a development run and the store agree.
7. [ ] 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.
8. [ ] Then run the ordinary release checklist above for the pro-m1 version. The subscription is attached to that version's review submission.