Initial Commit

This commit is contained in:
2026-05-27 23:29:53 +01:00
commit fbc70f96e6
10000 changed files with 1102152 additions and 0 deletions

View File

@@ -0,0 +1,50 @@
# Extend @suaveplan/react-ui with runtime theme-token mutator + extension-token surface
**Package**: `@suaveplan/react-ui`
**Status**: PROPOSED
**Type**: feature
- **Design**: ./design.md
- **Spec delta**: ./specs/react-ui/spec.md
## Why
The upcoming `@suaveplan/react-auth-whitelabel` package needs to push tenant-branded CSS custom properties (`--brand-primary`, `--brand-secondary`, `--brand-accent`, `--brand-font`) to `document.documentElement` at runtime, after fetching a tenant-branding JSON payload. The current genesis `ThemeProvider` only supports swapping between pre-registered `ThemePlugin` records: there is no runtime mutator for chassis tokens, and no path for arbitrary extension tokens that live outside the closed 19-key `ThemeTokenSet`. Upstream `react-auth-whitelabel` worked around this by writing to `document.documentElement.style` directly, bypassing the provider — a real architecture gap that prevents nesting, leaks variables on unmount, and is invisible to `useThemeTokens()` consumers. This change closes the gap so whitelabel can migrate without leaking CSS variables and so any future runtime-data-driven theming (A/B tests, user-customisable accent colour, calendar/widget micro-themes) has a first-class API.
## What changes
- ADDED: `useThemeMutator()` hook — returns a stable mutator handle `{ mutatorId, setChassisOverrides, setExtensionTokens, clear }` bound to the calling component's lifetime. On unmount, the mutator's contributions are removed automatically.
- ADDED: `useResolvedThemeTokens()` hook — returns `{ chassis: ThemeTokenSet, extensions: Readonly<Record<string, string>> }` reflecting the active theme merged with every mounted mutator's contributions (innermost wins per-key).
- ADDED: `REACT_UI.INVALID_EXTENSION_TOKEN_NAME` error code — thrown when an extension token name does not start with `--`.
- ADDED: subpath exports `./theme/use-theme-mutator` and `./theme/use-resolved-theme-tokens`.
- MODIFIED: `ThemeProvider` now drives a module-level mutation-layer stack: chassis tokens from the active `ThemePlugin` are applied as layer 0, every `useThemeMutator()` adds a layer scoped to its calling component, and on every layer mutation/unmount the merged result is re-applied to `document.documentElement`. Existing public surface (`themes`, `defaultTheme`, `setTheme`, `useThemeTokens`, FOUC script, reduced-motion behaviour) is **unchanged**.
- MODIFIED: `ThemeContextValue` gains a non-breaking `mutators` field exposing the layer-registration entry points used by the new hooks.
## Out of scope
- Persisting mutator state to `localStorage` — mutators are explicitly runtime-only because they are sourced from per-request payloads (tenant branding, A/B test bucket). A separate change can add an opt-in persistor.
- Server-side serialisation of mutator state into the FOUC script — mutators are post-hydration constructs; the FOUC script keeps its current single-theme-attribute scope.
- Variant overrides via mutators — `variantOverrides` remain a build-time property of `ThemePlugin`; runtime variant overrides require a separate provider channel (`useThemedVariants` is unchanged).
## Source mapping
Genesis-native extension to enable `@suaveplan/react-auth-whitelabel` migration; see `/mnt/s/web/suaveplan/packages/react/react-auth-whitelabel/src/context/branding-context.tsx` for the consumer pattern (a `useEffect` that writes `--brand-*` properties directly to `document.documentElement.style` from a fetched payload). No `enterprise/<pkg>.md` exists for the extension because it is genesis-original.
## Verification approach
- `src/theme/use-theme-mutator/use-theme-mutator.test.tsx` — covers all chassis-override and extension-token requirements; mount/unmount cleanup; composability under nested providers.
- `src/theme/use-resolved-theme-tokens/use-resolved-theme-tokens.test.tsx` — covers the read hook's merge semantics and reactivity.
- `src/theme/theme-provider/theme-provider.test.tsx` — extended with `it()` blocks asserting layer-zero semantics (active plugin renders verbatim when no mutators present) and that `setTheme(...)` re-merges over the live mutator stack.
- `src/errors/error-codes.test.ts` — pins the new `INVALID_EXTENSION_TOKEN_NAME` code into the registry snapshot.
## Migration impact
None for existing consumers — every new symbol is additive. Existing tests in `theme-provider.test.tsx`, `use-theme-tokens.test.tsx`, and `use-theme.test.tsx` continue to pass without modification. The whitelabel migration (handled in a separate change for `@suaveplan/react-auth-whitelabel`) will replace its direct `document.documentElement.style.setProperty` calls with `useThemeMutator().setExtensionTokens(...)`.
## Impact
### Affected dependents
- `@suaveplan/react-auth-whitelabel` (pending migration) — [x] no action needed (this change unblocks the migration; the whitelabel change set will consume the new hooks)
- `@suaveplan/react-ui-pack-auth` — [x] no action needed (does not write to `document.documentElement.style`)
- All other `react-ui` consumers — [x] no action needed (additive surface only)