le king fu
|
fd7e053239
|
feat(gating): license provider + entitlements matrix + useEntitlement (#297)
PR Check / rust (pull_request) Successful in 22m38s
PR Check / frontend (pull_request) Successful in 2m34s
Socle for tier-based feature gating (UI-only soft-paywall).
- LicenseContext: machine-level provider (createContext<T|null>, useReducer,
throwing consumer hook), mounted above ProfileProvider in main.tsx so a
profile switch (BrowserRouter key remount) does not reload the license.
Loads edition + info once; exposes { status, edition, features, info, error,
refresh, submitKey }. Boot-error recovery (CWE-703): neutral state + capped
exponential-backoff retry, never the upsell.
- shared/entitlements.ts: FeatureKey (kebab-case), ENTITLEMENTS matrix, pure
isEntitled() fail-closed in Free (CWE-863) — the features[] override is
ignored before edition==="free" is checked.
- useEntitlement(f): { allowed, ready } (ready = status==="ready"), synchronous.
- useIsPremium + its test migrated onto the context (drops the per-call double
invoke); LicenseCard consumes the context. useLicense.ts removed (fully
replaced, no remaining consumers).
- services/entitlements.test.ts: matrix, features[] override, override ignored
in Free, unknown-feature deny-all.
Resolves #297
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
2026-07-19 21:05:10 -04:00 |
|
le king fu
|
98f68f7a1f
|
feat(prices): useIsPremium hook from license.edition
PR Check / rust (push) Successful in 26m18s
PR Check / frontend (push) Successful in 2m37s
PR Check / rust (pull_request) Successful in 25m0s
PR Check / frontend (pull_request) Successful in 2m41s
- Reads useLicense().state.edition === 'premium'
- Ergonomic only — server enforces independently (ADR 0011)
- 3 vitest tests (premium, base, free)
- CLAUDE.md hook count 12 -> 13
Closes #157
|
2026-04-27 08:11:23 -04:00 |
|