Conversation
Adds the entitlement core for a freemium ClaudeBar Pro. Everything that ships today stays free; Pro will gate only NEW features. - LicenseService: the 3 Lemon Squeezy license calls (activate/validate/ deactivate). The license key is the credential (no API token in-app). - LicenseStore: @observable entitlement persisted in the Keychain (mirrors KeychainService), with offline grace — a network blip never locks a paying user (14d), a refund/expiry locks immediately. - ProConfig: checkout URL + price + the Pro feature list. - LicenseStoreTests: activation, authoritative revocation, both grace edges. - docs/PRO_SETUP.md: the Lemon Squeezy dashboard setup + test checklist. UI wiring (Settings Pro section + per-feature gates) is a deliberate follow-up so this core is reviewable and CI compiles it first.
ClaudeAPIClient already declares this extension in the same module; reuse it.
- ProSettingsSection: upgrade button (opens Lemon Squeezy checkout), license-key activation field, deactivate, and grace/status display. - SettingsView + PopoverView thread a shared LicenseStore through. - AppDelegate owns one LicenseStore and calls refresh() at launch. - en/ru Localizable.strings: the settings.pro.* keys. Feature GATING at each Pro feature's call site is the next step; this lands the purchase + activation UI so the store is testable end-to-end.
- foregroundStyle ternary: unify to Color.secondary/Color.red (was mixing HierarchicalShapeStyle and Color). - Drop the @mainactor LicenseStore() default arg from PopoverView/SettingsView inits (can't default-construct a main-actor type in a nonisolated init); callers (AppDelegate, previews) pass it explicitly.
Marking the PopoverView/SettingsView inits @mainactor lets the default LicenseStore() argument be constructed safely (the views are only used from MainActor contexts anyway), so existing ViewTests call sites that omit the license argument keep compiling.
Swift evaluates default argument values in a nonisolated context even when the init is @mainactor, so 'license: LicenseStore = LicenseStore()' can't compile against the actor-isolated LicenseStore.init. Use an optional 'license: LicenseStore? = nil' and build the default in the init body (which is @mainactor).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
First step of monetizing ClaudeBar: a freemium Pro tier. Everything that ships today stays free forever — Pro gates only new features. Payment via Lemon Squeezy (Merchant of Record: remits global tax, pays out to a non-US individual, no company needed).
What's in this PR (the entitlement core)
LicenseService— the 3 LS license calls (activate/validate/deactivate). The license key is the credential; no API token embedded in the app.LicenseStore—@Observableentitlement, persisted in the Keychain (mirrorsKeychainService). Implements offline grace: a network blip never locks a paying user (14-day window), but an authoritative refund/expiry/disable locks immediately.ProConfig— checkout URL, price string, and the Pro feature list (usage history+graphs, multi-account, spend alerts, low-credit alerts, CSV export).LicenseStoreTests— activation success/limit/empty, authoritative revocation, and both grace edges (blip-within-grace stays Pro; past-grace locks).docs/PRO_SETUP.md— the one-time LS dashboard setup + test-mode checklist.Draft, because two things remain
ProConfig) — seedocs/PRO_SETUP.md.SettingsView(upgrade button + key field + deactivate) and per-featureisProgates. Deliberately separate so this core compiles + is reviewed first.Note
I can't build Swift locally — relying on CI to compile + run
LicenseStoreTests. If anything's red I'll fix it.