--- 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`: `` + 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*