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:
@@ -379,10 +379,14 @@ private struct RecentBoardRow: View {
|
||||
|
||||
/// The app's preferences (⌘, — 11-command-nexus.md).
|
||||
///
|
||||
/// One control, which is the whole of v1: "Restore open boards at launch". The preference gates only
|
||||
/// whether the registry's open-now flags are *consulted* at launch — the flags themselves are
|
||||
/// maintained either way, which is what keeps crash recovery working for a user who has restoration
|
||||
/// turned off and then turns it back on.
|
||||
/// Two sections. The first is the one control v1 shipped with: "Restore open boards at launch",
|
||||
/// whose preference gates only whether the registry's open-now flags are *consulted* at launch — the
|
||||
/// flags themselves are maintained either way, which is what keeps crash recovery working for a user
|
||||
/// who has restoration turned off and then turns it back on.
|
||||
///
|
||||
/// The second is **where Lanework Pro lives** (12-editions.md ▸ Distribution: "purchased and managed
|
||||
/// in a Pro section of Settings (⌘,) — subscribe, manage, restore purchases"), and the third of the
|
||||
/// exactly three places the app names Pro at all — see `ProSettingsSection` for its own posture.
|
||||
struct SettingsView: View {
|
||||
|
||||
@AppStorage(AppPreferences.restoreOpenBoardsAtLaunchKey)
|
||||
@@ -395,11 +399,14 @@ struct SettingsView: View {
|
||||
} footer: {
|
||||
Text("On, the boards open at your last quit reopen automatically. Off, every launch starts at Welcome.")
|
||||
}
|
||||
|
||||
ProSettingsSection()
|
||||
}
|
||||
.formStyle(.grouped)
|
||||
// Font-derived: this pane is `.fixedSize()`, so a 420-point literal would clip its one
|
||||
// toggle's footer sentence at a large system text size with no way to resize out of it
|
||||
// (10-accessibility.md ▸ Text scaling).
|
||||
// Font-derived: this pane is `.fixedSize()`, so a 420-point literal would clip a footer
|
||||
// sentence at a large system text size with no way to resize out of it
|
||||
// (10-accessibility.md ▸ Text scaling). The height stays free — the Pro section's own height
|
||||
// depends on which of its three states it is in, and pinning it would clip the longest.
|
||||
.frame(width: BoardMetrics.em(32.3, bodyPointSize: BoardMetrics.bodyPointSize))
|
||||
.fixedSize()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user