Files
2026-05-27 23:29:53 +01:00

4.8 KiB

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<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.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/.