---
phase: 41-diagrams-icons-theme-engine
plan: "02"
subsystem: api
tags: [diagram-renderer, icon-renderer, playwright, dompurify, resvg, svgo, sharp, llm-synthesis, mermaid, tdd]

# Dependency graph
requires:
  - phase: 41-01
    provides: RenderResult/DiagramBundle/IconSetBundle types, renderer stubs, server deps installed

provides:
  - server/src/services/renderers/diagram-renderer.ts — LLM prompt synthesis + Playwright Mermaid render + DOMPurify + Resvg
  - server/src/services/renderers/icon-renderer.ts — LLM SVG icon generation + SVGO cleanup + sharp PNG rasterization
  - server/src/services/puter-inference.ts — shared non-streaming LLM completion helper (Puter API)
  - server/src/__tests__/diagram-renderer.test.ts — 18 tests for stripUnsafeDirectives, buildDiagramPrompt, renderDiagram
  - server/src/__tests__/icon-renderer.test.ts — 12 tests for validateAndCleanSvg, renderIconSet

affects: [41-03-theme, 41-05-ui-generator, 41-06-ui-theme]

# Tech tracking
tech-stack:
  added: []
  patterns:
    - "puterChatComplete: non-streaming LLM helper via PUTER_AUTH_TOKEN env var + Puter AI proxy (OpenAI-compatible)"
    - "stripUnsafeDirectives: regex-based Mermaid security — removes %%{init}%% blocks and click directives before render"
    - "renderDiagram: prompt -> LLM Mermaid synthesis -> stripUnsafeDirectives -> Playwright headless -> DOMPurify -> Resvg PNG"
    - "validateAndCleanSvg: SVGO preset-default + viewBox/xmlns normalization + drawable element presence check"
    - "renderIconSet: LLM JSON array -> validateAndCleanSvg each -> sharp rasterize 16/32/64 -> IconSetBundle"
    - "JSON retry pattern: parse fail -> retry LLM with explicit JSON-only follow-up message"

key-files:
  created:
    - server/src/services/renderers/diagram-renderer.ts
    - server/src/services/renderers/icon-renderer.ts
    - server/src/services/puter-inference.ts
    - server/src/__tests__/diagram-renderer.test.ts
    - server/src/__tests__/icon-renderer.test.ts
  modified: []

key-decisions:
  - "puter-inference.ts created as shared non-streaming helper — avoids duplicating Puter fetch boilerplate in each renderer"
  - "resolveBrowserPath uses built-in fs/path (not glob npm dep) — simpler, no new dependency"
  - "DOMPurify window cast uses 'any' — @types/dompurify WindowLike interface incompatible with JSDOM window; type-safe cast not possible"
  - "SVGO preset-default is applied before drawable element check — SVGO removes degenerate paths so test SVGs must use complete path data"

# Metrics
duration: 10min
completed: 2026-04-04
---

# Phase 41 Plan 02: Diagram Renderer + Icon Renderer Summary

**Diagram renderer synthesizes Mermaid from natural language via LLM (DIAG-01), strips unsafe directives (DIAG-05), renders SVG+PNG via Playwright+DOMPurify+Resvg; icon renderer generates SVG icon sets via LLM, cleans with SVGO, rasterizes to 3 PNG sizes via sharp**

## Performance

- **Duration:** ~10 min
- **Started:** 2026-04-04T20:36:38Z
- **Completed:** 2026-04-04T20:42:00Z
- **Tasks:** 2
- **Files created/modified:** 5

## Accomplishments

- Diagram renderer: full LLM->Mermaid->Playwright->SVG->PNG pipeline with security stripping
- Icon renderer: full LLM->JSON->SVGO->sharp->PNG bundle pipeline with retry and partial failure handling
- puter-inference.ts: reusable non-streaming LLM helper for all server-side renderers
- 30 total tests passing (18 diagram + 12 icon), all TDD RED->GREEN
- TypeScript passes cleanly with `pnpm tsc --noEmit`
- Both renderers replace their Plan 01 stubs and conform to RenderResult contract

## Task Commits

1. **Task 1: Diagram renderer with LLM synthesis + Playwright + security + tests** - `9c3146fd` (feat)
2. **Task 2: Icon renderer with LLM SVG generation + SVGO + PNG variants + tests** - `175dce3b` (feat)

## Files Created

- `server/src/services/renderers/diagram-renderer.ts` - Full implementation replacing stub from Plan 01
- `server/src/services/renderers/icon-renderer.ts` - Full implementation replacing stub from Plan 01
- `server/src/services/puter-inference.ts` - Shared non-streaming LLM completion via Puter AI proxy
- `server/src/__tests__/diagram-renderer.test.ts` - 18 tests: stripUnsafeDirectives (5), buildDiagramPrompt (7), renderDiagram (6)
- `server/src/__tests__/icon-renderer.test.ts` - 12 tests: validateAndCleanSvg (6), renderIconSet (6)

## Decisions Made

- Created `puter-inference.ts` as a shared helper instead of inlining Puter fetch in each renderer. All renderer LLM calls flow through this one module, which reads `PUTER_AUTH_TOKEN` from environment.
- Used Node.js `fs`/`path` for browser path resolution instead of adding `glob` as a dependency.
- DOMPurify type cast uses `any` because `@types/dompurify`'s `WindowLike` interface does not match JSDOM's window type exactly — this is a known type incompatibility.

## Deviations from Plan

### Auto-fixed Issues

**1. [Rule 3 - Blocking] Replaced glob import with built-in fs/path**
- **Found during:** Task 1 (GREEN phase)
- **Issue:** `import { glob } from "glob"` failed — `glob` is not installed in server package
- **Fix:** Implemented a simple two-level directory scan using Node.js `fs.readdirSync` and `fs.existsSync`
- **Files modified:** server/src/services/renderers/diagram-renderer.ts
- **Commit:** 9c3146fd

**2. [Rule 1 - Bug] Fixed test SVG for "adds xmlns when missing" test**
- **Found during:** Task 2 (GREEN phase test run)
- **Issue:** SVGO `preset-default` correctly removes degenerate SVG paths (e.g. `d="M12 2"` — just a move, no shape). The test used this degenerate path, so after SVGO the SVG had no drawable elements and `validateAndCleanSvg` returned `valid: false`.
- **Fix:** Updated test SVG to use a complete triangle path `d="M12 2L2 7l10 5z"` that SVGO preserves
- **Files modified:** server/src/__tests__/icon-renderer.test.ts
- **Commit:** 175dce3b

**3. [Rule 1 - Bug] Fixed DOMPurify window type cast**
- **Found during:** Task 1 tsc check
- **Issue:** `window as unknown as Window` caused TS2345 error — JSDOM's window type is not assignable to `WindowLike`
- **Fix:** Changed cast to `window as any` with eslint-disable comments
- **Files modified:** server/src/services/renderers/diagram-renderer.ts
- **Commit:** 175dce3b

---

**Total deviations:** 3 auto-fixed (Rules 1 and 3 — blocking and bug)
**Impact on plan:** No scope change. All fixes are minor corrections that maintain the intent of the plan.

## Known Stubs

None — both renderers are fully implemented. All Plan 01 stubs have been replaced.

## User Setup Required

- `PUTER_AUTH_TOKEN` environment variable must be set for LLM calls to work at runtime
- `npx playwright install chromium` must be run (or `PLAYWRIGHT_BROWSERS_PATH` set) for diagram rendering at runtime

## Next Phase Readiness

- Plan 41-03 (theme renderer) can now follow the same pattern: `puterChatComplete` for LLM + `RenderResult` return
- puter-inference.ts is ready for reuse in theme-renderer.ts
- All Phase 41 content types (diagram, icon, theme) share the same job runner → renderer dispatch pattern

---
*Phase: 41-diagrams-icons-theme-engine*
*Completed: 2026-04-04*
