253 lines
10 KiB
Markdown
253 lines
10 KiB
Markdown
---
|
|
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"
|
|
---
|
|
|
|
<objective>
|
|
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.
|
|
</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/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
|
|
|
|
<interfaces>
|
|
<!-- From server/src/services/content-job-runner.ts -->
|
|
```typescript
|
|
export async function renderContent(
|
|
_jobType: string,
|
|
_input: Record<string, unknown>,
|
|
): 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
|
|
```
|
|
</interfaces>
|
|
</context>
|
|
|
|
<tasks>
|
|
|
|
<task type="auto">
|
|
<name>Task 1: Install dependencies and add shadcn components</name>
|
|
<files>server/package.json, ui/src/components/ui/progress.tsx, ui/src/components/ui/toggle.tsx</files>
|
|
<read_first>server/package.json, ui/components.json</read_first>
|
|
<action>
|
|
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')"
|
|
```
|
|
</action>
|
|
<verify>
|
|
<automated>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"</automated>
|
|
</verify>
|
|
<acceptance_criteria>
|
|
- `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
|
|
</acceptance_criteria>
|
|
<done>All 5 server deps installed and importable; shadcn progress and toggle components added</done>
|
|
</task>
|
|
|
|
<task type="auto">
|
|
<name>Task 2: Shared types, content-job-runner switch, useContentJob hook, API helpers</name>
|
|
<files>server/src/services/renderers/types.ts, server/src/services/content-job-runner.ts, ui/src/hooks/useContentJob.ts, ui/src/api/contentJobs.ts</files>
|
|
<read_first>server/src/services/content-job-runner.ts, server/src/routes/content-jobs.ts, ui/src/api/client.ts, ui/src/hooks/useChatMessages.ts</read_first>
|
|
<action>
|
|
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<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>
|