--- phase: 41-diagrams-icons-theme-engine plan: "01" type: execute wave: 1 depends_on: [] files_modified: - server/package.json - ui/src/components/ui/progress.tsx - ui/src/components/ui/toggle.tsx - server/src/services/content-job-runner.ts - server/src/services/renderers/types.ts - ui/src/hooks/useContentJob.ts - ui/src/api/contentJobs.ts autonomous: true requirements: [DIAG-01, DIAG-02, ICON-01, THEME-01] must_haves: truths: - "Server has culori, @resvg/resvg-js, wcag-contrast, svgo, playwright-core installed" - "renderContent switch dispatches diagram, icon-set, and theme-palette job types to renderer imports" - "useContentJob hook submits a job and subscribes to SSE progress" artifacts: - path: "server/src/services/renderers/types.ts" provides: "Shared bundle type definitions for all renderers" exports: ["DiagramBundle", "IconSetBundle", "ThemePaletteBundle", "RenderResult"] - path: "ui/src/hooks/useContentJob.ts" provides: "React hook for submitting content jobs and tracking SSE progress" exports: ["useContentJob"] - path: "ui/src/api/contentJobs.ts" provides: "API helpers for content job endpoints" exports: ["submitContentJob", "getContentJob", "getContentJobAsset"] key_links: - from: "server/src/services/content-job-runner.ts" to: "server/src/services/renderers/*" via: "switch(jobType) dynamic import" pattern: "case .diagram.*renderDiagram" --- Install all Phase 41 dependencies, define shared type contracts for renderer bundles, extend the content-job-runner switch to dispatch to renderers, create the useContentJob UI hook, and add shadcn progress/toggle components. Purpose: Foundation layer so all three renderer plans and both UI plans can build against stable contracts. Output: Installed deps, bundle type definitions, wired job runner, UI hook + API helpers, shadcn components. @$HOME/.claude/get-shit-done/workflows/execute-plan.md @$HOME/.claude/get-shit-done/templates/summary.md @.planning/PROJECT.md @.planning/ROADMAP.md @.planning/STATE.md @.planning/phases/41-diagrams-icons-theme-engine/41-RESEARCH.md @.planning/phases/40-job-infrastructure/40-01-SUMMARY.md @.planning/phases/40-job-infrastructure/40-02-SUMMARY.md ```typescript export async function renderContent( _jobType: string, _input: Record, ): Promise<{ filename: string; contentType: string; buffer: Buffer }> { // Stub -- phases 41-45 will add real renderers keyed by jobType return { filename: "placeholder.txt", contentType: "text/plain", buffer: Buffer.from("placeholder output") }; } export const contentJobRunner = { dispatch(db: Db, storage: StorageService, job: ContentJob): void { void runJob(db, storage, job); }, }; ``` ```typescript // POST /api/companies/:companyId/content-jobs -> 202 { jobId, status, createdAt } // GET /api/companies/:companyId/content-jobs/:jobId/events -> SSE stream ``` Task 1: Install dependencies and add shadcn components server/package.json, ui/src/components/ui/progress.tsx, ui/src/components/ui/toggle.tsx server/package.json, ui/components.json 1. Install server dependencies: ```bash pnpm --filter server add culori @resvg/resvg-js wcag-contrast svgo playwright-core ``` Verify playwright-core version matches the installed @playwright/test version in root package.json (should be 1.58.x — check `pnpm list @playwright/test` first). 2. Install shadcn UI components: ```bash pnpm --filter ui exec shadcn add progress pnpm --filter ui exec shadcn add toggle ``` 3. Verify culori ESM import works with NodeNext module resolution by running: ```bash cd /opt/nexus && pnpm --filter server exec tsx -e "import { converter, formatHex } from 'culori'; const toOklch = converter('oklch'); console.log(formatHex(toOklch('#1e66f5')))" ``` If this fails with ERR_REQUIRE_ESM, use `import culori from 'culori/fn'` instead in subsequent tasks. 4. Verify @resvg/resvg-js native binding works: ```bash cd /opt/nexus && pnpm --filter server exec tsx -e "import { Resvg } from '@resvg/resvg-js'; console.log('resvg OK')" ``` cd /opt/nexus && pnpm --filter server exec tsx -e "import { converter } from 'culori'; import { Resvg } from '@resvg/resvg-js'; import { optimize } from 'svgo'; import wcagContrast from 'wcag-contrast'; console.log('ALL_DEPS_OK')" && test -f ui/src/components/ui/progress.tsx && test -f ui/src/components/ui/toggle.tsx && echo "SHADCN_OK" - `pnpm --filter server list culori` shows culori installed - `pnpm --filter server list @resvg/resvg-js` shows @resvg/resvg-js installed - `pnpm --filter server list wcag-contrast` shows wcag-contrast installed - `pnpm --filter server list svgo` shows svgo installed - `pnpm --filter server list playwright-core` shows playwright-core installed - `ui/src/components/ui/progress.tsx` exists - `ui/src/components/ui/toggle.tsx` exists - culori ESM import resolves without error under NodeNext All 5 server deps installed and importable; shadcn progress and toggle components added Task 2: Shared types, content-job-runner switch, useContentJob hook, API helpers server/src/services/renderers/types.ts, server/src/services/content-job-runner.ts, ui/src/hooks/useContentJob.ts, ui/src/api/contentJobs.ts server/src/services/content-job-runner.ts, server/src/routes/content-jobs.ts, ui/src/api/client.ts, ui/src/hooks/useChatMessages.ts 1. Create `server/src/services/renderers/types.ts` with the shared bundle type contracts: ```typescript export interface RenderResult { filename: string; contentType: string; buffer: Buffer; } export interface DiagramBundle { type: "diagram-bundle"; svgBase64: string; pngBase64: string; mermaidSource: string; stripped: boolean; } export interface IconSetBundle { type: "icon-set-bundle"; style: "outline" | "filled" | "rounded"; icons: Array<{ name: string; svgSource: string; pngs: Record; // "16" | "32" | "64" -> base64 }>; } export interface ThemePaletteBundle { type: "theme-palette-bundle"; seedHex: string; palette: PaletteRole[]; exports: { css: string; tailwind: string; vscode: string; json: string }; } export interface PaletteRole { name: string; dark: { oklch: string; hex: string; wcagAA: boolean }; light: { oklch: string; hex: string; wcagAA: boolean }; } export type ContentBundle = DiagramBundle | IconSetBundle | ThemePaletteBundle; ``` 2. Modify `server/src/services/content-job-runner.ts`: - Replace the stub `renderContent` with a switch that imports from renderer files: ```typescript import type { RenderResult } from "./renderers/types.js"; export async function renderContent( jobType: string, input: Record, ): Promise { switch (jobType) { case "diagram": { const { renderDiagram } = await import("./renderers/diagram-renderer.js"); return renderDiagram(input); } case "icon-set": { const { renderIconSet } = await import("./renderers/icon-renderer.js"); return renderIconSet(input); } case "theme-palette": { const { renderThemePalette } = await import("./renderers/theme-renderer.js"); return renderThemePalette(input); } default: throw new Error(`Unknown jobType: ${jobType}`); } } ``` - Keep the rest of the file (runJob, contentJobRunner) unchanged. 3. Create `ui/src/api/contentJobs.ts`: - `submitContentJob(companyId, jobType, input)` — POST to `/companies/${companyId}/content-jobs`, returns `{ jobId, status }`. - `getContentJob(companyId, jobId)` — GET single job. - `getContentJobAsset(companyId, assetId)` — GET asset data (returns blob URL for download). - Use the existing `api` client from `ui/src/api/client.ts`. 4. Create `ui/src/hooks/useContentJob.ts`: - State: `{ jobId, status, progress, resultAssetId, errorMessage }`. - `submit(jobType, input)` — calls submitContentJob, opens EventSource to SSE endpoint, updates state on events. - `reset()` — clears state back to idle. - EventSource listens for `status` events, updates progress (queued=5%, running=50%, done=100%). - Closes EventSource on done/failed and on component unmount (useEffect cleanup). - Uses companyId from existing context (read how useChatMessages or similar hooks get companyId). cd /opt/nexus && pnpm tsc --noEmit --project server/tsconfig.json && pnpm tsc --noEmit --project ui/tsconfig.json - `server/src/services/renderers/types.ts` exports `RenderResult`, `DiagramBundle`, `IconSetBundle`, `ThemePaletteBundle`, `PaletteRole`, `ContentBundle` - `server/src/services/content-job-runner.ts` contains `case "diagram":` and `case "icon-set":` and `case "theme-palette":` - `ui/src/hooks/useContentJob.ts` exports `useContentJob` - `ui/src/api/contentJobs.ts` exports `submitContentJob`, `getContentJob` - TypeScript compiles without errors for both server and ui Shared type contracts defined; content-job-runner dispatches to 3 renderer imports; UI hook and API helpers ready for consumption by UI plans - `pnpm tsc --noEmit --project server/tsconfig.json` passes - `pnpm tsc --noEmit --project ui/tsconfig.json` passes - All 5 server deps importable via tsx one-liner - shadcn progress and toggle components exist in ui/src/components/ui/ - All Phase 41 dependencies installed and verified working - Bundle type contracts defined and exported - content-job-runner switch dispatches diagram/icon-set/theme-palette (dynamic imports will fail at runtime until renderers exist -- that is expected) - useContentJob hook compiles and exports - API helpers compile and export After completion, create `.planning/phases/41-diagrams-icons-theme-engine/41-01-SUMMARY.md`