---
phase: 41-diagrams-icons-theme-engine
plan: "05"
subsystem: ui
tags: [theme-engine, oklch, wcag, shadcn, react-testing-library, jsdom, tdd]

# Dependency graph
requires:
  - phase: 41-01
    provides: useContentJob hook, contentJobs API, progress.tsx, toggle.tsx shadcn components
  - phase: 41-03
    provides: theme-renderer, PaletteRole type, ThemePaletteBundle, nexus-settings customTheme

provides:
  - ui/src/components/ThemeSeedInput.tsx — color picker + hex input, debounced onChange
  - ui/src/components/ThemePaletteGrid.tsx — swatch grid with WCAG AA/Fails AA badges, empty state
  - ui/src/components/ThemePreviewPanel.tsx — scoped CSS injection via ref (NOT document.documentElement)
  - ui/src/components/ThemePreviewPanel.test.tsx — 7 tests for THEME-04 scoped injection
  - ui/src/components/ThemeExportTabs.tsx — CSS/Tailwind/VS Code/JSON tabs with copy buttons
  - ui/src/components/ThemeApplyConfirmDialog.tsx — Apply theme / Keep current confirmation dialog
  - ui/src/context/ThemeContext.tsx — extended with custom theme type and applyCustomTheme()
  - ui/src/pages/ContentStudio.tsx — Themes tab fully wired
  - ui/src/api/contentJobs.ts — submitContentJob, getContentJob, getContentJobAsset
  - ui/src/hooks/useContentJob.ts — SSE EventSource progress hook

affects: [theme-apply-flow, nexus-settings]

# Tech tracking
tech-stack:
  added:
    - "@testing-library/react@^16.3.2 (UI devDep)"
    - "@testing-library/jest-dom@^6.9.1 (UI devDep)"
    - "jsdom@^28.1.0 (UI devDep)"
    - "@vitejs/plugin-react added to ui/vitest.config.ts for JSX transform"
  patterns:
    - "ThemePreviewPanel pattern: useRef + useEffect + container.style.setProperty() — CSS vars scoped to .nexus-theme-preview, NEVER on document.documentElement"
    - "applyCustomTheme pattern: sets CSS vars on document.documentElement only on explicit user apply action"
    - "TDD in jsdom environment: // @vitest-environment jsdom override in individual test files"
    - "useContentJob hook: companyId parameter, SSE EventSource progress tracking, fetchAsset helper"

key-files:
  created:
    - ui/src/components/ThemeSeedInput.tsx
    - ui/src/components/ThemePaletteGrid.tsx
    - ui/src/components/ThemePreviewPanel.tsx
    - ui/src/components/ThemePreviewPanel.test.tsx
    - ui/src/components/ThemeExportTabs.tsx
    - ui/src/components/ThemeApplyConfirmDialog.tsx
    - ui/src/pages/ContentStudio.tsx
    - ui/src/api/contentJobs.ts
    - ui/src/hooks/useContentJob.ts
    - ui/src/components/ui/progress.tsx
    - ui/src/components/ui/toggle.tsx
  modified:
    - ui/src/context/ThemeContext.tsx (extended Theme type, added applyCustomTheme, on-mount customTheme restore)
    - ui/vitest.config.ts (added @vitejs/plugin-react for JSX transform in tests)
    - ui/package.json (added @testing-library/react, jest-dom, jsdom as devDeps)
    - pnpm-lock.yaml

key-decisions:
  - "Used @testing-library/react + jsdom for ThemePreviewPanel tests instead of project's renderToStaticMarkup pattern — required because tests need to verify imperative DOM style.setProperty calls, which only work in a real DOM environment"
  - "ThemeContext extended from light/dark to light/dark/custom — custom treated as dark-mode for toggle/classList purposes"
  - "applyCustomTheme() sets CSS vars on document.documentElement (global apply) while ThemePreviewPanel sets on scoped container ref (preview only) — two distinct patterns for two distinct purposes"
  - "contentJobs.ts and useContentJob.ts created as prerequisites in this worktree — these files from Plan 41-01 are not available in this git worktree"
  - "On-mount customTheme restore: ThemeProvider fetches /api/nexus/settings on mount if stored theme is 'custom' to rehydrate CSS vars across page loads"

# Metrics
duration: 17min
completed: 2026-04-04
---

# Phase 41 Plan 05: Theme UI Components Summary

**All theme UI components built and wired: seed input, palette grid with WCAG badges, scoped live preview (with TDD test coverage for THEME-04), 4-format export tabs, apply confirmation dialog, ThemeContext custom theme extension, ContentStudio Themes tab fully functional**

## Performance

- **Duration:** ~17 min
- **Started:** 2026-04-04T20:49:03Z
- **Completed:** 2026-04-04T21:06:26Z
- **Tasks:** 2
- **Files modified:** 14

## Accomplishments

- `ThemeSeedInput`: `<input type="color">` + hex text Input side-by-side with `htmlFor="seed-color"` label, debounced onChange (150ms), helper text "We'll generate a full palette in OKLCH."
- `ThemePaletteGrid`: Dark and light swatch rows, 40×40px swatches, hex labels, WCAG AA / Fails AA badges using `--chart-2` green and `--destructive` red. Empty state with exact copywriting from UI-SPEC.
- `ThemePreviewPanel`: `.nexus-theme-preview` container with `useRef`, CSS vars injected imperatively via `container.style.setProperty()` only on the scoped element (never `document.documentElement`). Mini Nexus UI mock (sidebar + card + button). `aria-live="polite"` announces "Palette updated" on palette changes.
- `ThemePreviewPanel.test.tsx`: 7 TDD tests (jsdom environment) verifying class, aria-label, aria-live, dark/light CSS var values, and document scope isolation — all passing.
- `ThemeExportTabs`: CSS Variables / Tailwind Config / VS Code Theme / JSON tabs with pre/code blocks. Copy button per tab with `aria-label="Copy {tab name}"`, "Copied!" feedback (2s), keyboard accessible.
- `ThemeApplyConfirmDialog`: Dialog with "Apply theme?" heading, "This will update your Nexus color scheme. You can revert from Settings." body, "Apply theme" primary button, "Keep current" ghost button.
- `ThemeContext` extended: `Theme` type = `"light" | "dark" | "custom"`. `applyCustomTheme(palette, variant)` sets CSS vars on `document.documentElement`, updates state to "custom", stores to localStorage. On-mount: if stored theme is "custom", fetches `/api/nexus/settings` to restore `customTheme.palette` vars.
- `ContentStudio` page: Themes tab with full generate/preview/export/apply flow using `useContentJob` SSE hook.
- `contentJobs.ts` and `useContentJob.ts` created as prerequisites (not present in this worktree from Plan 41-01).
- `progress.tsx` and `toggle.tsx` shadcn components added.

## Task Commits

1. **TDD RED — failing ThemePreviewPanel tests + infrastructure** - `78e50189` (test)
2. **Task 1 + Task 2: All components + ThemeContext + ContentStudio** - `05ce37df` (feat)

## Files Created/Modified

- `ui/src/components/ThemeSeedInput.tsx` — Color picker + hex input, debounced onChange
- `ui/src/components/ThemePaletteGrid.tsx` — Swatch grid with WCAG badges
- `ui/src/components/ThemePreviewPanel.tsx` — Scoped CSS injection preview panel
- `ui/src/components/ThemePreviewPanel.test.tsx` — 7 TDD tests for THEME-04
- `ui/src/components/ThemeExportTabs.tsx` — 4-format export tabs with copy
- `ui/src/components/ThemeApplyConfirmDialog.tsx` — Confirm dialog
- `ui/src/context/ThemeContext.tsx` — Extended with custom theme type + applyCustomTheme
- `ui/src/pages/ContentStudio.tsx` — Themes tab fully wired
- `ui/src/api/contentJobs.ts` — Content job API helpers
- `ui/src/hooks/useContentJob.ts` — SSE progress hook
- `ui/src/components/ui/progress.tsx` — shadcn Progress component
- `ui/src/components/ui/toggle.tsx` — shadcn Toggle component
- `ui/vitest.config.ts` — Added @vitejs/plugin-react for JSX transform
- `ui/package.json` — Added testing devDeps
- `pnpm-lock.yaml` — Updated lockfile

## Decisions Made

- `@testing-library/react` and `jsdom` installed as devDeps — the project's `renderToStaticMarkup` pattern cannot test imperative DOM calls (`style.setProperty`); THEME-04 specifically requires verifying that CSS variables are set on the scoped container, not on `document.documentElement`. jsdom environment is required.
- ThemeContext `Theme` type extended to include `"custom"` — "custom" is treated as dark for `classList.toggle("dark")` purposes so the app shell continues to use dark color scheme when a custom palette is active.
- Two CSS injection patterns established: `ThemePreviewPanel` → scoped container ref (live preview only); `applyCustomTheme()` → `document.documentElement` (global apply on user confirmation).
- `contentJobs.ts`, `useContentJob.ts`, `progress.tsx`, `toggle.tsx` created in this plan — these were supposed to come from Plan 41-01 but that plan's work exists only on the parallel branch, not in this worktree.

## Deviations from Plan

### Auto-fixed Issues

**1. [Rule 3 - Blocking] Added @testing-library/react + jsdom for TDD requirement**
- **Found during:** TDD RED phase
- **Issue:** The plan requires testing `style.setProperty` DOM calls on a scoped container element. The project's existing test pattern uses `// @vitest-environment node` with `renderToStaticMarkup` (server-side rendering), which cannot test imperative DOM manipulation. No `@testing-library/react` or jsdom was installed.
- **Fix:** Added `@testing-library/react@^16.3.2`, `@testing-library/jest-dom@^6.9.1`, and `jsdom@^28.1.0` as UI devDeps. Updated `ui/vitest.config.ts` to include `@vitejs/plugin-react` for JSX transform support. Individual test files use `// @vitest-environment jsdom` override to avoid affecting existing node-env tests.
- **Files modified:** ui/package.json, ui/vitest.config.ts, pnpm-lock.yaml
- **Verification:** All 7 ThemePreviewPanel tests pass

**2. [Rule 3 - Blocking] Created contentJobs.ts, useContentJob.ts, progress.tsx, toggle.tsx as prerequisites**
- **Found during:** Task 1 (ContentStudio wiring)
- **Issue:** These files from Plan 41-01 are not present in this worktree (parallel branch divergence, same issue documented in 41-03 SUMMARY)
- **Fix:** Created all four files from scratch matching the interfaces documented in Plan 41-01 SUMMARY
- **Files modified:** ui/src/api/contentJobs.ts, ui/src/hooks/useContentJob.ts, ui/src/components/ui/progress.tsx, ui/src/components/ui/toggle.tsx

**3. [Rule 1 - Bug] Removed @testing-library/jest-dom top-level import from test file**
- **Found during:** TDD RED phase
- **Issue:** `import "@testing-library/jest-dom"` calls `expect.extend()` globally before vitest's expect is initialized, causing `ReferenceError: expect is not defined`
- **Fix:** Removed the import; tests use vitest's built-in assertions which cover all required test cases without jest-dom matchers

---

**Total deviations:** 3 auto-fixed (Rules 1, 3, 3)
**Impact on plan:** All plan goals met. Testing infrastructure added as required for THEME-04 verification. No plan scope expanded.

## Known Stubs

None — all components are fully implemented. ThemePreviewPanel renders a real mini Nexus UI mock with CSS variables applied from the palette. ContentStudio is wired to the real content job API.

## Self-Check: PASSED

- FOUND: ui/src/components/ThemeSeedInput.tsx
- FOUND: ui/src/components/ThemePaletteGrid.tsx
- FOUND: ui/src/components/ThemePreviewPanel.tsx
- FOUND: ui/src/components/ThemePreviewPanel.test.tsx
- FOUND: ui/src/components/ThemeExportTabs.tsx
- FOUND: ui/src/components/ThemeApplyConfirmDialog.tsx
- FOUND: ui/src/context/ThemeContext.tsx (extended)
- FOUND: ui/src/pages/ContentStudio.tsx
- FOUND: ui/src/api/contentJobs.ts
- FOUND: ui/src/hooks/useContentJob.ts
- FOUND: ui/src/components/ui/progress.tsx
- FOUND: ui/src/components/ui/toggle.tsx
- FOUND commit: 78e50189 (test - TDD RED)
- FOUND commit: 05ce37df (feat - Task 1+2 implementation)
- All 7 ThemePreviewPanel tests pass
- TypeScript compiles without errors (pnpm tsc --noEmit --project ui/tsconfig.json from worktree)

---
*Phase: 41-diagrams-icons-theme-engine*
*Completed: 2026-04-04*
