# Tasks — extend-react-ui-runtime-theme-mutation ## 1. Foundation - [ ] Add `INVALID_EXTENSION_TOKEN_NAME` to `REACT_UI_CODES` registry; update the registry doc-comment. - [ ] Update `src/errors/error-codes.test.ts` to pin the new code. ## 2. Layer stack (module-level) - [ ] Create `src/theme/layer-stack/layer-stack.types.ts` with `MutationLayer`, `LayerOrigin`, `LayerStackListener`. - [ ] Create `src/theme/layer-stack/layer-stack.ts` exporting `pushLayer`, `removeLayer`, `updateLayer`, `getMergedResolved`, `subscribe`. Tracks the `Set` 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.tsx` to push a `MutationLayer` for the active plugin on mount and update it on `setTheme(...)` / theme change. Unmount removes the layer. Maintain `providerId` (uuid). Reduced-motion + font-stack writes also go through the layer (so they participate in the unmount cleanup). - [ ] Extend `ThemeContextValue` with a `mutators` field carrying `{ providerId, push, update, remove }` private hooks for `useThemeMutator` consumption. Keep all existing fields and types unchanged. - [ ] Extended `theme-provider.test.tsx` tests: - [ ] "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` + `useEffect` lifecycle. Validates extension keys; throws `ReactUiError(REACT_UI.INVALID_EXTENSION_TOKEN_NAME)`. Calls `clear` on 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 via `useSyncExternalStore`. - [ ] 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-mutator` and `./theme/use-resolved-theme-tokens` to `package.json` exports. - [ ] 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 for `useTheme` / `useThemeTokens`. ## 7. Story - [ ] `theme-provider.stories.tsx` — add a story showing live runtime token-override input (text fields → calls `setExtensionTokens`). ## 8. Spec reconciliation + gates - [ ] Verify every `### Requirement` in `specs/react-ui/spec.md` has 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.md` delta into canonical `openspec/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/`.