10 KiB
| phase | plan | type | wave | depends_on | files_modified | autonomous | requirements | must_haves | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 41-diagrams-icons-theme-engine | 01 | execute | 1 |
|
true |
|
|
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.
<execution_context> @$HOME/.claude/get-shit-done/workflows/execute-plan.md @$HOME/.claude/get-shit-done/templates/summary.md </execution_context>
@.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); }, };
<!-- From server/src/routes/content-jobs.ts -->
```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).
-
Install shadcn UI components:
pnpm --filter ui exec shadcn add progress pnpm --filter ui exec shadcn add toggle -
Verify culori ESM import works with NodeNext module resolution by running:
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. -
Verify @resvg/resvg-js native binding works:
cd /opt/nexus && pnpm --filter server exec tsx -e "import { Resvg } from '@resvg/resvg-js'; console.log('resvg OK')"
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<string, string>; // "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<string, unknown>,
): Promise<RenderResult> {
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).
</action>
<verify>
<automated>cd /opt/nexus && pnpm tsc --noEmit --project server/tsconfig.json && pnpm tsc --noEmit --project ui/tsconfig.json</automated>
</verify>
<acceptance_criteria>
- `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
</acceptance_criteria>
<done>Shared type contracts defined; content-job-runner dispatches to 3 renderer imports; UI hook and API helpers ready for consumption by UI plans</done>
</task>
</tasks>
<verification>
- `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/
</verification>
<success_criteria>
- 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
</success_criteria>
<output>
After completion, create `.planning/phases/41-diagrams-icons-theme-engine/41-01-SUMMARY.md`
</output>