---
phase: 41-diagrams-icons-theme-engine
plan: "04"
type: execute
wave: 3
depends_on: ["41-01", "41-02"]
files_modified:
  - ui/src/pages/ContentStudio.tsx
  - ui/src/components/DiagramGeneratePanel.tsx
  - ui/src/components/DiagramPreview.tsx
  - ui/src/components/DiagramSourcePanel.tsx
  - ui/src/components/DiagramSourcePanel.test.tsx
  - ui/src/components/IconGeneratePanel.tsx
  - ui/src/components/IconResultGrid.tsx
  - ui/src/components/IconDownloadBar.tsx
  - ui/src/App.tsx
autonomous: true
requirements: [DIAG-01, DIAG-03, DIAG-04, ICON-01, ICON-02, ICON-03]
must_haves:
  truths:
    - "User can describe a diagram in a textarea and click Generate Diagram to submit a content job"
    - "Rendered diagram SVG appears in the preview panel after job completes"
    - "User can expand the collapsible source panel to view and edit the Mermaid source"
    - "User can select a diagram type from Architecture, Flowchart, ERD, Sequence, Mind Map"
    - "User can describe icons and receive an SVG grid with download options at 16/32/64 PNG"
    - "User can select multiple icons and bulk-download them"
  artifacts:
    - path: "ui/src/pages/ContentStudio.tsx"
      provides: "Tabbed page hosting Diagrams, Icons, Themes tabs"
      min_lines: 30
    - path: "ui/src/components/DiagramGeneratePanel.tsx"
      provides: "Prompt textarea + diagram type selector + Generate button + progress"
    - path: "ui/src/components/DiagramPreview.tsx"
      provides: "SVG render area with download buttons"
    - path: "ui/src/components/DiagramSourcePanel.tsx"
      provides: "Collapsible editable Mermaid source textarea"
    - path: "ui/src/components/DiagramSourcePanel.test.tsx"
      provides: "Tests for DIAG-03 collapsible source panel behavior"
    - path: "ui/src/components/IconGeneratePanel.tsx"
      provides: "Description textarea + style/count selectors + Generate button"
    - path: "ui/src/components/IconResultGrid.tsx"
      provides: "CSS grid of icon cards with selection checkboxes"
    - path: "ui/src/components/IconDownloadBar.tsx"
      provides: "Sticky bar for downloading selected icons"
  key_links:
    - from: "ui/src/pages/ContentStudio.tsx"
      to: "ui/src/App.tsx"
      via: "Route registration"
      pattern: "path.*content-studio"
    - from: "ui/src/components/DiagramGeneratePanel.tsx"
      to: "ui/src/hooks/useContentJob.ts"
      via: "useContentJob hook submit('diagram', input)"
      pattern: "useContentJob"
---

<objective>
Build the ContentStudio page with Diagrams and Icons tabs. Implement all diagram UI components (generate panel, preview, source editor) and all icon UI components (generate panel, result grid, download bar). Wire to useContentJob hook from Plan 01. Include DiagramSourcePanel test (DIAG-03 Wave 0 requirement).

Purpose: User-facing UI for diagram and icon generation.
Output: ContentStudio page with Diagrams and Icons tabs fully functional, with DiagramSourcePanel test coverage.
</objective>

<execution_context>
@$HOME/.claude/get-shit-done/workflows/execute-plan.md
@$HOME/.claude/get-shit-done/templates/summary.md
</execution_context>

<context>
@.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-02-SUMMARY.md

<interfaces>
<!-- From ui/src/hooks/useContentJob.ts (created in Plan 01) -->
```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<string, unknown>) => Promise<void>;
  reset: () => void;
}
```

<!-- From ui/src/api/contentJobs.ts (created in Plan 01) -->
```typescript
export function submitContentJob(companyId: string, jobType: string, input: Record<string, unknown>): Promise<{ jobId: string; status: string }>;
export function getContentJob(companyId: string, jobId: string): Promise<ContentJob>;
export function getContentJobAsset(companyId: string, assetId: string): Promise<Blob>;
```

<!-- Bundle types the UI must parse from server response -->
```typescript
interface DiagramBundle { type: "diagram-bundle"; svgBase64: string; pngBase64: string; mermaidSource: string; stripped: boolean; }
interface IconSetBundle { type: "icon-set-bundle"; style: string; icons: Array<{ name: string; svgSource: string; pngs: Record<string, string> }>; }
```
</interfaces>
</context>

<tasks>

<task type="auto" tdd="true">
  <name>Task 1: ContentStudio page + Diagram UI components + DiagramSourcePanel test</name>
  <files>ui/src/pages/ContentStudio.tsx, ui/src/components/DiagramGeneratePanel.tsx, ui/src/components/DiagramPreview.tsx, ui/src/components/DiagramSourcePanel.tsx, ui/src/components/DiagramSourcePanel.test.tsx, ui/src/App.tsx</files>
  <read_first>ui/src/App.tsx, ui/src/pages/Dashboard.tsx, ui/src/components/ui/tabs.tsx, ui/src/components/ui/collapsible.tsx, ui/src/components/ui/select.tsx, ui/src/components/ui/textarea.tsx, ui/src/components/ui/button.tsx, ui/src/components/ui/progress.tsx, ui/src/hooks/useContentJob.ts, ui/src/index.css</read_first>
  <behavior>
    - DiagramSourcePanel renders a collapsible trigger with text "View Mermaid source"
    - Clicking the trigger expands to show a monospace textarea with the Mermaid source
    - When expanded, trigger text changes to "Hide source"
    - A "Copy source" button with aria-label="Copy source" is present in expanded state
    - Editing the textarea content marks it dirty and shows a "Re-render diagram" button
    - "Re-render diagram" button calls the onRerender callback with the edited source
  </behavior>
  <action>
1. Create `ui/src/components/DiagramSourcePanel.test.tsx` FIRST (Wave 0 for DIAG-03):
   - Test that collapsed state shows "View Mermaid source" trigger text
   - Test that clicking trigger expands to show textarea with provided mermaidSource prop
   - Test that expanded state shows "Hide source" trigger text
   - Test that "Copy source" button has `aria-label="Copy source"`
   - Test that editing textarea and blurring shows "Re-render diagram" button
   - Test that clicking "Re-render diagram" calls onRerender with the edited source text
   - Use @testing-library/react for rendering and user interactions

2. Create `ui/src/pages/ContentStudio.tsx`:
   - Tabbed layout using shadcn Tabs component
   - Three tabs: "Diagrams", "Icons", "Themes"
   - Each tab renders its respective panel component
   - Page heading: "Content Studio" (h1, 20px semibold per UI spec)
   - Get companyId from route params or company context (check how Dashboard.tsx gets it)

3. Register route in `ui/src/App.tsx`:
   - Add route `/:companyId/content-studio` pointing to ContentStudio
   - Add navigation entry if a sidebar/nav component exists (check existing nav structure)

4. Create `ui/src/components/DiagramGeneratePanel.tsx`:
   - Textarea (4 rows) for diagram description prompt
   - Select component for diagram type: Architecture, Flowchart, ERD, Sequence, Mind Map
   - "Generate Diagram" Button (primary) -- disabled during generation, shows "Generating..." with spinner
   - Progress bar (shadcn progress) below button, visible during queued/running states
   - On submit: call `useContentJob.submit("diagram", { prompt: promptText, diagramType, darkMode: false })`
   - On done: fetch asset, parse DiagramBundle JSON, pass to DiagramPreview
   - On error: show error message "Render failed -- {detail}. Try again." below progress bar
   - If directives were stripped (bundle.stripped === true), show muted helper text: "Unsafe directives were removed before rendering."
   - Empty state: heading "No diagram yet", body "Describe an architecture, flow, or sequence and we'll render it."

5. Create `ui/src/components/DiagramPreview.tsx`:
   - Renders SVG inside container with className "paperclip-mermaid" (reuse existing CSS)
   - SVG content is pre-sanitized server-side via DOMPurify (DIAG-05). Render the trusted sanitized SVG string.
   - Two ghost buttons below: "Download SVG", "Download PNG"
   - Download SVG: create blob from decoded svgBase64, trigger download as `diagram.svg`
   - Download PNG: create blob from decoded pngBase64, trigger download as `diagram.png`

6. Create `ui/src/components/DiagramSourcePanel.tsx` (must pass the tests from step 1):
   - Collapsible component (shadcn collapsible)
   - Trigger label: "View Mermaid source" (collapsed) / "Hide source" (expanded) with chevron icon
   - Content: monospace Textarea (14px, read/write) showing mermaidSource
   - "Copy source" IconButton at top-right with `aria-label="Copy source"` and `title="Copy source"`
   - When source is modified (dirty), show "Re-render diagram" Button (secondary, xs) at bottom-right
   - Re-render submits a new job with the edited source (via `onRerender` prop, which parent wires to `useContentJob.submit("diagram", { source: editedSource })` — note: `source` field skips LLM synthesis on server)
   - Height transition: 250ms ease (respect prefers-reduced-motion)

   Copywriting: Use exact strings from UI-SPEC copywriting contract table.
   Accessibility: All aria-labels per UI-SPEC accessibility section.
  </action>
  <verify>
    <automated>cd /opt/nexus && pnpm --filter ui exec vitest run src/components/DiagramSourcePanel.test.tsx && pnpm tsc --noEmit --project ui/tsconfig.json</automated>
  </verify>
  <acceptance_criteria>
    - `grep "content-studio" ui/src/App.tsx` matches (route registered)
    - `grep "Generate Diagram" ui/src/components/DiagramGeneratePanel.tsx` matches
    - `grep "paperclip-mermaid" ui/src/components/DiagramPreview.tsx` matches
    - `grep 'aria-label="Copy source"' ui/src/components/DiagramSourcePanel.tsx` matches
    - `grep "View Mermaid source" ui/src/components/DiagramSourcePanel.tsx` matches
    - `grep "Re-render diagram" ui/src/components/DiagramSourcePanel.tsx` matches
    - `grep "Unsafe directives were removed" ui/src/components/DiagramGeneratePanel.tsx` matches
    - `grep "No diagram yet" ui/src/components/DiagramGeneratePanel.tsx` matches
    - DiagramSourcePanel.test.tsx exists and all tests pass
    - TypeScript compiles without errors
  </acceptance_criteria>
  <done>ContentStudio page registered at /:companyId/content-studio with Diagrams tab fully wired: prompt input, type selector, generate button, progress bar, SVG preview, download buttons, collapsible source editor with test coverage (DIAG-03)</done>
</task>

<task type="auto">
  <name>Task 2: Icon UI components (generate panel, result grid, download bar)</name>
  <files>ui/src/components/IconGeneratePanel.tsx, ui/src/components/IconResultGrid.tsx, ui/src/components/IconDownloadBar.tsx</files>
  <read_first>ui/src/pages/ContentStudio.tsx, ui/src/hooks/useContentJob.ts, ui/src/components/ui/select.tsx, ui/src/components/ui/checkbox.tsx, ui/src/components/ui/card.tsx, ui/src/components/ui/button.tsx, ui/src/components/ui/progress.tsx</read_first>
  <action>
1. Create `ui/src/components/IconGeneratePanel.tsx`:
   - Card container
   - Description textarea (3 rows)
   - Style selector (Select: Outline, Filled, Rounded)
   - Count selector (Select: 1, 4, 8, 16)
   - "Generate Icons" Button (primary) -- disabled during generation, shows "Generating..."
   - Progress bar below button during generation
   - On submit: call `useContentJob.submit("icon-set", { description, style, count })`
   - On done: fetch asset, parse IconSetBundle JSON, pass icons to IconResultGrid
   - On error: show "Render failed -- {detail}. Try again."
   - Empty state: heading "No icons yet", body "Describe what you need and we'll generate a cohesive set."

2. Create `ui/src/components/IconResultGrid.tsx`:
   - Props: `icons: Array<{ name: string; svgSource: string; pngs: Record<string, string> }>`, `selectedIds: Set<string>`, `onToggle: (name: string) => void`
   - CSS grid: 4 columns on desktop (`grid-cols-4`), 2 on mobile (`grid-cols-2`)
   - Each cell: card-colored Card, SVG preview centered (render the server-sanitized SVG source string), icon name label below (text-xs)
   - Checkbox on card hover (top-left, 16px) with `aria-label="Select {icon name}"`
   - On coarse pointer (`@media (pointer: coarse)`): checkbox always visible
   - Hover reveals download row at bottom of card: SVG, PNG 16, PNG 32, PNG 64 as small ghost buttons
   - Each download button creates a blob from the appropriate base64 data and triggers download

3. Create `ui/src/components/IconDownloadBar.tsx`:
   - Props: `selectedCount: number`, `onDownload: (format: string) => void`, `onClear: () => void`
   - Sticky bar at bottom of panel (position: sticky, bottom: 0, z-10)
   - Only visible when selectedCount > 0
   - "Download selected ({N})" Button (primary)
   - Format selector (Select: SVG, PNG 16, PNG 32, PNG 64)
   - "Clear selection" link button
   - Download creates individual files (or a zip for multiple) with the chosen format

4. Wire IconGeneratePanel into ContentStudio "Icons" tab. Manage icon selection state (selectedIds Set) in IconGeneratePanel, pass down to IconResultGrid and IconDownloadBar.

   Copywriting: Use exact strings from UI-SPEC copywriting contract.
   Accessibility: All aria-labels per UI-SPEC accessibility section.
  </action>
  <verify>
    <automated>cd /opt/nexus && pnpm tsc --noEmit --project ui/tsconfig.json</automated>
  </verify>
  <acceptance_criteria>
    - `grep "Generate Icons" ui/src/components/IconGeneratePanel.tsx` matches
    - `grep "No icons yet" ui/src/components/IconGeneratePanel.tsx` matches
    - `grep "grid-cols-4" ui/src/components/IconResultGrid.tsx` matches
    - `grep 'aria-label.*Select' ui/src/components/IconResultGrid.tsx` matches
    - `grep "Download selected" ui/src/components/IconDownloadBar.tsx` matches
    - `grep "Clear selection" ui/src/components/IconDownloadBar.tsx` matches
    - TypeScript compiles without errors
  </acceptance_criteria>
  <done>Icons tab fully wired: description input, style/count selectors, generate button, 4-column grid with selection, bulk download bar with format choice</done>
</task>

</tasks>

<verification>
- `pnpm --filter ui exec vitest run src/components/DiagramSourcePanel.test.tsx` — all DiagramSourcePanel tests pass
- `pnpm tsc --noEmit --project ui/tsconfig.json` passes
- ContentStudio page has 3 tabs with Diagrams and Icons functional
- All copywriting matches UI-SPEC exactly
- All aria-labels present per accessibility spec
</verification>

<success_criteria>
- User can navigate to content-studio, see tabbed interface with Diagrams and Icons
- Diagram generation flow: type + describe -> generate -> see SVG -> download SVG/PNG -> edit source -> re-render (DIAG-01, DIAG-03, DIAG-04)
- DiagramSourcePanel has test coverage for collapsible behavior, copy, and re-render (DIAG-03)
- Icon generation flow: describe + style + count -> generate -> see grid -> select -> download (ICON-01, ICON-02, ICON-03)
- Empty states, error states, and loading states all handled per UI spec
</success_criteria>

<output>
After completion, create `.planning/phases/41-diagrams-icons-theme-engine/41-04-SUMMARY.md`
</output>
