4.8 KiB
4.8 KiB
Tasks — extend-react-ui-runtime-theme-mutation
1. Foundation
- Add
INVALID_EXTENSION_TOKEN_NAMEtoREACT_UI_CODESregistry; update the registry doc-comment. - Update
src/errors/error-codes.test.tsto pin the new code.
2. Layer stack (module-level)
- Create
src/theme/layer-stack/layer-stack.types.tswithMutationLayer,LayerOrigin,LayerStackListener. - Create
src/theme/layer-stack/layer-stack.tsexportingpushLayer,removeLayer,updateLayer,getMergedResolved,subscribe. Tracks theSet<string>of every CSS-var name ever touched; on every change re-applies merged state to the bound root element and removes any tracked name not present in the merged set. - Co-locate
.md(≥200 words; basic + advanced examples). layer-stack.test.ts— runner-agnostic, covers push/pop/update/merge order; tracked-name cleanup on removeLayer.layer-stack/index.ts— explicit named re-exports only.
3. Provider integration
- Modify
theme-provider.tsxto push aMutationLayerfor the active plugin on mount and update it onsetTheme(...)/ theme change. Unmount removes the layer. MaintainproviderId(uuid). Reduced-motion + font-stack writes also go through the layer (so they participate in the unmount cleanup). - Extend
ThemeContextValuewith amutatorsfield carrying{ providerId, push, update, remove }private hooks foruseThemeMutatorconsumption. Keep all existing fields and types unchanged. - Extended
theme-provider.test.tsxtests:- "applying the active plugin writes all 19 chassis tokens"
- "switching themes re-applies merged result with mutators still on top"
- "unmounting the provider clears all 19 chassis tokens"
4. useThemeMutator
src/theme/use-theme-mutator/use-theme-mutator.types.ts—ThemeMutatorHandle.src/theme/use-theme-mutator/use-theme-mutator.ts— implementation.useRef+useEffectlifecycle. Validates extension keys; throwsReactUiError(REACT_UI.INVALID_EXTENSION_TOKEN_NAME). Callsclearon unmount.- Co-located
.md(basic = static prop; advanced = runtime fetch + apply). .test.tsx— at least:- "setChassisOverrides writes Partial on document.documentElement"
- "setExtensionTokens writes arbitrary -- properties on document.documentElement"
- "extension token without -- prefix throws INVALID_EXTENSION_TOKEN_NAME"
- "clear() removes all of this mutator's contributions"
- "unmount removes all of this mutator's contributions"
- "nested mutators stack innermost-wins per key"
- "nested mutator unmount restores outer mutator's value"
- "extension tokens are namespace-orthogonal to chassis tokens"
- "setExtensionTokens replaces previous extension tokens (removed keys are cleared)"
- "throws REACT_UI.NO_PROVIDER outside a ThemeProvider"
index.ts— explicit named re-exports.
5. useResolvedThemeTokens
src/theme/use-resolved-theme-tokens/use-resolved-theme-tokens.ts— subscribes to layer-stack notifier viauseSyncExternalStore.- Co-located
.md. .test.tsx:- "returns the active plugin's tokens when no mutators present"
- "reflects mutator overrides in chassis"
- "reflects mutator extension tokens"
- "throws REACT_UI.NO_PROVIDER outside a ThemeProvider"
index.ts.
6. Subpath exports + barrel
- Add
./theme/use-theme-mutatorand./theme/use-resolved-theme-tokenstopackage.jsonexports. - Re-export new hooks + types from
src/theme/index.ts(explicit named). - Re-export from
src/index.ts(top-level barrel) if precedent is set foruseTheme/useThemeTokens.
7. Story
theme-provider.stories.tsx— add a story showing live runtime token-override input (text fields → callssetExtensionTokens).
8. Spec reconciliation + gates
- Verify every
### Requirementinspecs/react-ui/spec.mdhas a matching**Verified by:**it(...)title that exists in the codebase. bun run check(biome).bunx turbo lint typecheck test --filter=@suaveplan/react-ui --force.bunx turbo test:coverage --filter=@suaveplan/react-ui— no regression on per-metric coverage.bun scripts/openspec-conformance.ts @suaveplan/react-ui— 0 errors.bunx turbo build --filter=@suaveplan/react-ui --force.bun scripts/lint/check-test-runner-imports.ts— 0 violations.
9. Archival
- Fold
specs/react-ui/spec.mddelta into canonicalopenspec/specs/react-ui/spec.md(capability is being instantiated for the first time alongside this change; if a prior unarchived change already established the canonical file, merge instead of overwrite). - Move
openspec/changes/extend-react-ui-runtime-theme-mutation/→openspec/archive/2026-05-19-extend-react-ui-runtime-theme-mutation/.