--- phase: 41-diagrams-icons-theme-engine plan: "05" type: execute wave: 3 depends_on: ["41-01", "41-03"] files_modified: - ui/src/components/ThemeSeedInput.tsx - ui/src/components/ThemePaletteGrid.tsx - ui/src/components/ThemePreviewPanel.tsx - ui/src/components/ThemeExportTabs.tsx - ui/src/components/ThemeApplyConfirmDialog.tsx - ui/src/context/ThemeContext.tsx - ui/src/components/ThemePreviewPanel.test.tsx autonomous: true requirements: [THEME-01, THEME-04, THEME-05, THEME-07] must_haves: truths: - "User picks a seed color and sees a full palette grid with dark and light variants" - "WCAG AA pass/fail badges are shown on each swatch" - "Theme preview updates live without full page refresh, scoped to .nexus-theme-preview" - "User can export palette as CSS variables, Tailwind config, VS Code theme, or JSON via tabbed interface" - "User can apply the generated theme to their Nexus instance with a confirmation dialog" artifacts: - path: "ui/src/components/ThemeSeedInput.tsx" provides: "Color picker + hex text input for seed color" - path: "ui/src/components/ThemePaletteGrid.tsx" provides: "Swatch grid with WCAG badges for dark and light variants" - path: "ui/src/components/ThemePreviewPanel.tsx" provides: "Scoped mini Nexus UI mock with injected CSS variables" - path: "ui/src/components/ThemePreviewPanel.test.tsx" provides: "Tests for THEME-04 scoped CSS variable injection" - path: "ui/src/components/ThemeExportTabs.tsx" provides: "Tabbed code blocks for CSS, Tailwind, VS Code, JSON exports" - path: "ui/src/components/ThemeApplyConfirmDialog.tsx" provides: "Confirmation dialog before applying theme to Nexus" - path: "ui/src/context/ThemeContext.tsx" provides: "Extended to support custom theme token injection" key_links: - from: "ui/src/components/ThemePreviewPanel.tsx" to: ".nexus-theme-preview container" via: "container.style.setProperty() in useEffect" pattern: "setProperty.*--background" - from: "ui/src/components/ThemeApplyConfirmDialog.tsx" to: "server /api/nexus/settings" via: "PATCH request with customTheme payload" pattern: "customTheme" --- Build all theme UI components: seed input, palette grid with WCAG badges, scoped live preview, export tabs, and apply confirmation dialog. Extend ThemeContext to support custom theme injection. Include ThemePreviewPanel test (THEME-04 Wave 0 requirement). Purpose: User-facing UI for theme generation, preview, export, and application. Output: Themes tab fully functional in ContentStudio with live preview, apply flow, and preview panel test coverage. @$HOME/.claude/get-shit-done/workflows/execute-plan.md @$HOME/.claude/get-shit-done/templates/summary.md @.planning/PROJECT.md @.planning/ROADMAP.md @.planning/phases/41-diagrams-icons-theme-engine/41-UI-SPEC.md @.planning/phases/41-diagrams-icons-theme-engine/41-RESEARCH.md @.planning/phases/41-diagrams-icons-theme-engine/41-01-SUMMARY.md @.planning/phases/41-diagrams-icons-theme-engine/41-03-SUMMARY.md ```typescript export function useContentJob(companyId: string): { state: { jobId: string | null; status: "idle" | "queued" | "running" | "done" | "failed"; progress: number; resultAssetId: string | null; errorMessage: string | null }; submit: (jobType: string, input: Record) => Promise; reset: () => void; } ``` ```typescript interface ThemePaletteBundle { type: "theme-palette-bundle"; seedHex: string; palette: PaletteRole[]; exports: { css: string; tailwind: string; vscode: string; json: string }; } interface PaletteRole { name: string; dark: { oklch: string; hex: string; wcagAA: boolean }; light: { oklch: string; hex: string; wcagAA: boolean }; } ``` ```typescript export type Theme = "catppuccin-mocha" | "tokyo-night" | "catppuccin-latte"; export const THEME_META: Record; export function ThemeProvider({ children }: { children: ReactNode }): JSX.Element; export function useTheme(): { theme: Theme; setTheme: (t: Theme) => void }; ``` ```typescript const ROLE_TO_TOKEN: Record = { "background": "--background", "surface": "--card", "overlay": "--secondary", "text": "--foreground", "accent-1": "--primary", "accent-2": "--accent", "accent-3": "--muted", }; ``` Task 1: Theme seed input, palette grid, preview panel (with test), and export tabs 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/pages/ContentStudio.tsx, ui/src/hooks/useContentJob.ts, ui/src/context/ThemeContext.tsx, ui/src/index.css, ui/src/components/ui/tabs.tsx, ui/src/components/ui/toggle.tsx, ui/src/components/ui/badge.tsx, ui/src/components/ui/progress.tsx, ui/src/components/ui/tooltip.tsx - ThemePreviewPanel renders a container with className "nexus-theme-preview" and aria-label="Theme preview" - When palette prop changes, CSS variables are set on the .nexus-theme-preview container element (NOT on document.documentElement) - For dark variant, container.style.setProperty("--background", palette[0].dark.hex) is called (background role) - For light variant, container.style.setProperty("--background", palette[0].light.hex) is called - Container includes an aria-live="polite" announcer that says "Palette updated" when palette changes - CSS variables are ONLY set on the scoped container ref, never on document.documentElement 1. Create `ui/src/components/ThemePreviewPanel.test.tsx` FIRST (Wave 0 for THEME-04): - Test that component renders a container with className "nexus-theme-preview" - Test that container has aria-label="Theme preview" - Test that passing a palette prop calls style.setProperty on the container element for each role token - Test that dark variant uses role.dark.hex values - Test that light variant uses role.light.hex values - Test that aria-live="polite" region exists and announces "Palette updated" on palette change - Test that document.documentElement.style.setProperty is NOT called (scoping check) - Use @testing-library/react; mock or spy on HTMLElement.prototype.style.setProperty to verify calls 2. Create `ui/src/components/ThemeSeedInput.tsx`: - Props: `value: string`, `onChange: (hex: string) => void` - `` styled with focus ring, 48px height (touch target) - Associated ` cd /opt/nexus && pnpm --filter ui exec vitest run src/components/ThemePreviewPanel.test.tsx && pnpm tsc --noEmit --project ui/tsconfig.json - `grep 'htmlFor="seed-color"' ui/src/components/ThemeSeedInput.tsx` matches - `grep "nexus-theme-preview" ui/src/components/ThemePreviewPanel.tsx` matches - `grep "setProperty" ui/src/components/ThemePreviewPanel.tsx` matches - `grep 'aria-live="polite"' ui/src/components/ThemePreviewPanel.tsx` matches - `grep 'aria-label="Copy' ui/src/components/ThemeExportTabs.tsx` matches - `grep "Copied!" ui/src/components/ThemeExportTabs.tsx` matches - `grep "AA" ui/src/components/ThemePaletteGrid.tsx` matches - `grep "Fails AA" ui/src/components/ThemePaletteGrid.tsx` matches - `grep "No palette yet" ui/src/components/ThemePaletteGrid.tsx` matches - ThemePreviewPanel.test.tsx exists and all tests pass - TypeScript compiles without errors Theme tab shows seed input, palette grid with WCAG badges, scoped live preview (with test coverage for THEME-04), and 4-format export tabs with copy buttons Task 2: Apply theme flow (confirm dialog + ThemeContext extension + settings PATCH) ui/src/components/ThemeApplyConfirmDialog.tsx, ui/src/context/ThemeContext.tsx ui/src/context/ThemeContext.tsx, ui/src/components/ui/dialog.tsx, ui/src/components/ui/button.tsx, server/src/services/nexus-settings.ts, ui/src/api/client.ts 1. Create `ui/src/components/ThemeApplyConfirmDialog.tsx`: - Props: `open: boolean`, `onConfirm: () => void`, `onCancel: () => void` - Dialog component with: - Heading: "Apply theme?" - Body: "This will update your Nexus color scheme. You can revert from Settings." - Confirm button: "Apply theme" (primary, NOT destructive -- this is reversible) - Cancel button: "Keep current" (ghost variant) - On confirm: call onConfirm (parent handles the PATCH and ThemeContext update) 2. Extend `ui/src/context/ThemeContext.tsx`: - Add "custom" to the Theme union type: `export type Theme = "catppuccin-mocha" | "tokyo-night" | "catppuccin-latte" | "custom"` - Add `THEME_META["custom"]` entry: `{ label: "Custom", dark: true, bg: "#1e1e2e", primary: "#89b4fa" }` (defaults, overridden by actual palette) - Add `applyCustomTheme(palette: PaletteRole[], variant: "dark" | "light"): void` to the context value - `applyCustomTheme` implementation: - Set CSS variables on `document.documentElement` using ROLE_TO_TOKEN mapping - Update theme state to "custom" - Store in localStorage as "custom" - On provider mount: check if stored theme is "custom" -- if so, fetch nexus settings to get customTheme palette and apply it - Add `PaletteRole` type to the exports (or import from a shared types file) 3. Wire "Apply to Nexus" button in the Themes tab of ContentStudio: - Button: "Apply to Nexus" (primary, full-width at panel bottom) -- only visible when palette exists - Click opens ThemeApplyConfirmDialog - On confirm: a. PATCH `/api/nexus/settings` with `{ customTheme: { seedHex, palette } }` using the api client b. Call `applyCustomTheme(palette, variant)` from ThemeContext c. Show toast: "Theme applied. Reload to see full effect." (use the app's toast system -- check how toasts are done in existing code) d. Close dialog Copywriting: Use exact strings from UI-SPEC copywriting contract. cd /opt/nexus && pnpm tsc --noEmit --project ui/tsconfig.json - `grep "Apply theme" ui/src/components/ThemeApplyConfirmDialog.tsx` matches - `grep "Keep current" ui/src/components/ThemeApplyConfirmDialog.tsx` matches - `grep "custom" ui/src/context/ThemeContext.tsx` matches (custom theme type) - `grep "applyCustomTheme" ui/src/context/ThemeContext.tsx` matches - `grep "customTheme" ui/src/context/ThemeContext.tsx` matches (fetches from settings) - `grep "Theme applied" ui/src/pages/ContentStudio.tsx` or the component that triggers the toast matches - TypeScript compiles without errors Apply theme flow complete: confirm dialog, ThemeContext supports custom theme, PATCH to settings, CSS variables injected on document root, toast notification - `pnpm --filter ui exec vitest run src/components/ThemePreviewPanel.test.tsx` — ThemePreviewPanel tests pass - `pnpm tsc --noEmit --project ui/tsconfig.json` passes - Themes tab has seed input, palette grid, live preview, export tabs, and apply flow - ThemePreviewPanel injects CSS only to .nexus-theme-preview (not document.documentElement) - ThemeApplyConfirmDialog applies theme to document.documentElement on confirm - All copywriting and accessibility requirements from UI-SPEC met - User picks a seed color and receives a full palette with WCAG badges (THEME-01, THEME-04) - ThemePreviewPanel has test coverage verifying scoped CSS injection (THEME-04) - Export works for all 4 formats (THEME-05) - Apply theme persists to settings and updates Nexus UI (THEME-07) - Preview is scoped to .nexus-theme-preview container (THEME-04) After completion, create `.planning/phases/41-diagrams-icons-theme-engine/41-05-SUMMARY.md`