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>
23 lines
685 B
TypeScript
23 lines
685 B
TypeScript
import React from "react";
|
|
import ReactDOM from "react-dom/client";
|
|
import App from "./App";
|
|
import { LicenseProvider } from "./contexts/LicenseContext";
|
|
import { ProfileProvider } from "./contexts/ProfileContext";
|
|
import ErrorBoundary from "./components/shared/ErrorBoundary";
|
|
import { initLogCapture } from "./services/logService";
|
|
import "./i18n/config";
|
|
import "./styles.css";
|
|
|
|
initLogCapture();
|
|
|
|
ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(
|
|
<React.StrictMode>
|
|
<LicenseProvider>
|
|
<ProfileProvider>
|
|
<ErrorBoundary>
|
|
<App />
|
|
</ErrorBoundary>
|
|
</ProfileProvider>
|
|
</LicenseProvider>
|
|
</React.StrictMode>,
|
|
);
|