Files
lanework/RELEASE.md
T
rzen 3b19883593 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
2026-07-30 18:14:58 -04:00

69 lines
8.0 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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-*.swift` scripts, 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), `-exportArchive` with `scripts/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.example` committed) — 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`**:
```sh
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.
1. [ ] All milestone work committed and pushed (build number = commit count).
2. [ ] Unit suite green: `xcodebuild test -project Kanban.xcodeproj -scheme Kanban -destination 'platform=macOS,arch=arm64' -only-testing:KanbanTests`.
3. [ ] 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).
4. [ ] 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.
5. [ ] 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).
6. [ ] Archive dry run: `scripts/release.sh` at least through the archive step completes locally.
7. **— PUSH POINT (everything below touches the live record) —**
8. [ ] `scripts/release.sh` — archive + upload the build.
9. [ ] Metadata dry run, then push (`asc-metadata.swift --platform macos`, commands above).
10. [ ] Verify pricing (currently free, carried from 1.x) and availability (ALL territories) still match intent — Pro will be the paid tier later.
11. [ ] 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).
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 (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: 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.