---
id: M002
title: "React Frontend — Import & Convert UI + Design Canvas"
status: complete
completed_at: 2026-03-26T06:06:46.753Z
key_decisions:
  - D005: Vite + React + TS with plain CSS modules, Vitest, raw fetch — minimal dependency surface
  - D006: JSON.stringify params stabilization in useDebouncedTrace to avoid infinite re-render loops
  - D007: Canvas state uses useReducer + useRef — reducer for renders, ref for undo/redo history. Select/deselect excluded from undo stack.
  - D008: Canvas objects use TypeScript discriminated union on type field with shared BaseCanvasObject
  - D009: Convert-to-paths uses single onConvertToPath callback producing ImageObject with SVG Blob URL
key_files:
  - app/src/App.tsx — ViewState routing, SVG/metadata state for view handoff
  - app/src/api/engine.ts — Typed API client (getPresets, traceImage, simplifyVector)
  - app/src/types/engine.ts — TypeScript interfaces for engine API types
  - app/src/types/canvas.ts — Discriminated union type system for canvas objects
  - app/src/hooks/useDebouncedTrace.ts — Debounced trace with AbortController and params stabilization
  - app/src/hooks/useCanvasState.ts — Central canvas state management with undo/redo
  - app/src/views/ImportConvert.tsx — View 1: file upload, preset selection, live preview
  - app/src/views/DesignCanvas.tsx — View 2: canvas container wiring KonvaStage + panels
  - app/src/components/canvas/KonvaStage.tsx — Konva rendering: artboard, objects, selection, tools
  - app/src/components/canvas/ShapeProperties.tsx — Property editor with text controls + Convert to Paths
  - app/src/utils/fontService.ts — Font loading, caching, text-to-path conversion via opentype.js
  - app/src/utils/artboardShapes.ts — Artboard shape presets, SVG path generators, unit conversion
  - app/src/utils/alignment.ts — Alignment and distribution functions
  - app/vite.config.ts — Vite config with React plugin, dev proxy, Vitest config
  - engine/main.py — CORSMiddleware for browser API access
lessons_learned:
  - L009: useCallback-based hooks with object params cause infinite re-render loops — use JSON.stringify in useEffect deps
  - L010: jest-canvas-mock crashes in Vitest because it calls jest.fn() — use vitest-canvas-mock instead
  - P006-P012: Seven reusable patterns established covering React hook stabilization, Vite proxy, CSS modules, opentype.js integration, font coordinate system, letter spacing, and exhaustive switch updates for discriminated unions
---

# M002: React Frontend — Import & Convert UI + Design Canvas

**Built the complete React frontend with Import & Convert view (View 1) integrating the engine API for live vectorization preview, and a Konva.js-powered Design Canvas (View 2) with artboard shapes, basic shapes, text objects with font loading and text-to-path conversion, layers, alignment tools, and undo/redo.**

## What Happened

M002 delivered the React frontend for the Kerf Engine application across three slices, adding 54 files and 10,721 lines of code with 95 passing tests and zero TypeScript errors.

**S01 — Import & Convert UI (View 1):** Established the Vite + React + TypeScript foundation (D005) with a typed API client for the engine (getPresets, traceImage, simplifyVector with AbortSignal support). Built the complete Import & Convert view: drag-and-drop file upload with thumbnail preview and SVG detection, preset selector that fetches from the engine API, mode-aware parameter sliders (potrace vs vtracer controls), responsive SVG preview, and a color-coded output stats bar. The central innovation was the useDebouncedTrace hook which debounces API calls on parameter/file changes with AbortController cancellation, routing SVG uploads to the simplify endpoint instead of trace. Solved a critical infinite re-render loop by stabilizing object params via JSON.stringify in useEffect dependencies (D006, P006). Added CORSMiddleware to the engine for browser access and a Vite dev proxy for seamless API calls (P007). The Use This button passes SVG + TraceMetadata to View 2.

**S02 — Design Canvas Core (View 2):** Created the Konva.js-powered 2D design environment. Established a discriminated union type system for canvas objects (rect, circle, ellipse, line, image) with shared BaseCanvasObject (D007/D008). Built useCanvasState — the central state management hook using useReducer + useRef — with full CRUD, selection (excluded from undo stack), undo/redo (history capped at 50), reorder, and visibility/lock toggle. Created artboard shape utilities (shield, pennant SVG paths, dimension presets, unit conversion). Built KonvaStage with artboard rendering, shape creation tools, drag/transform handlers, Transformer synced to selected nodes, and rubber-band multi-select. Delivered a four-panel system: ObjectPanel (z-order layer management), AlignmentBar (6 align + 2 distribute + center-on-artboard), CanvasToolbar (tools, undo/redo, grid, zoom), and ShapeProperties (stroke/fill/dimensions editing). Added keyboard shortcuts (Ctrl+Z, Ctrl+Shift+Z, Delete, Escape, Ctrl+A). SVG import from View 1 uses Blob URL → Image element → Konva.Image with auto-scale to fit artboard.

**S03 — Text System + Font Loading:** Extended the canvas with text objects via opentype.js v1.3.4. Built fontService — a utility module that fetches .ttf fonts from the public directory, caches parsed Font objects with in-flight deduplication, and converts text to SVG path data. Solved the font-to-canvas coordinate transformation (Y-axis flip: ascender * scale - font_y) and implemented manual per-character glyph positioning for letter spacing support (P010, P011). Bundled three OFL-licensed Google Fonts (Roboto, Open Sans, Lato). Added TextObject to the discriminated union with exhaustive switch updates across 6 files (P012). Built text property controls (content, font family, size, letter spacing, line height) and Convert to Paths — an atomic text-to-image replacement via SVG Blob URL (D009).

The cross-slice integration chain works: View 1 uploads → engine vectorizes → live preview → Use This → Artboard Setup → Canvas with imported SVG → add shapes/text → manipulate with tools/alignment/properties → undo/redo through history.

## Success Criteria Results

### Success Criteria Results

- **✅ View 1 — Upload a PNG, see live preview with preset selection and slider controls, click 'Use This' to advance to canvas**
  - FileUpload supports drag-drop + file input with thumbnail preview
  - PresetSelector fetches from engine API, renders cards, auto-selects 'sign'
  - ParameterSliders show mode-aware controls (potrace vs vtracer params)
  - useDebouncedTrace provides live re-trace on param changes with AbortController
  - SvgPreview renders responsive SVG with loading/error states
  - OutputInfoBar shows color-coded stats (green/yellow/red)
  - Use This button passes SVG + TraceMetadata to View 2
  - Evidence: 23 tests pass (engine API client, useDebouncedTrace hook, OutputInfoBar)

- **✅ View 2 — Create artboards in each shape, add/move/resize shapes, multi-select with alignment, undo/redo through history**
  - ArtboardSetup modal for shape/size/unit selection (rect, circle, ellipse, shield, pennant)
  - KonvaStage renders artboards and 5 object types with Transformer + rubber-band select
  - ObjectPanel provides z-order layer management with reorder, visibility/lock
  - AlignmentBar: 6 alignment + 2 distribute + center-on-artboard operations
  - ShapeProperties: stroke/fill/dimensions/line-style editing
  - CanvasToolbar: tool switcher, undo/redo, grid toggle, zoom
  - Keyboard shortcuts: Ctrl+Z/Ctrl+Shift+Z undo/redo, Delete remove, Escape deselect, Ctrl+A select all
  - Evidence: 48 canvas tests pass (20 useCanvasState, 15 artboardShapes, 13 alignment)

- **✅ View 2 Text — Add text objects, select fonts from picker, change size/spacing, convert text to paths**
  - TextObject in discriminated union with text-specific fields
  - fontService loads/caches .ttf fonts, converts text to SVG path data
  - Text tool in CanvasToolbar, text rendering in KonvaStage
  - Text property controls: content textarea, font family dropdown, font size, letter spacing, line height
  - Convert to Paths: confirmation dialog → fontService.textToPathData() → SVG Blob URL → ImageObject replacement
  - Evidence: 24 fontService tests pass (registry, loading, caching, path conversion, coordinate system)

- **✅ Overall — React frontend with View 1 and View 2 integrated via ViewState routing**
  - App.tsx manages ViewState (import → canvas → export) with SVG/metadata state handoff
  - 95 total tests pass, zero TypeScript errors under strict mode

## Definition of Done Results

### Definition of Done Results

- **✅ All slices complete:** S01 ✅, S02 ✅, S03 ✅ — all three slices marked done in roadmap
- **✅ All slice summaries exist:** S01-SUMMARY.md, S02-SUMMARY.md, S03-SUMMARY.md confirmed on disk
- **✅ All tests pass:** 95/95 tests across 7 test files (vitest run exit 0)
- **✅ Zero TypeScript errors:** tsc --noEmit exits cleanly under strict mode
- **✅ Code changes verified:** 54 files changed, 10,721 insertions since M001 (non-.gsd/ files)
- **✅ Cross-slice integration verified:**
  - S01→S02: onUseThis callback passes SVG + TraceMetadata from ImportConvert to DesignCanvas via App.tsx
  - S02→S03: TextObject extends CanvasObject union; text tool wired through KonvaStage, ObjectPanel, ShapeProperties, AlignmentBar, CanvasToolbar

## Requirement Outcomes

No formal requirements (RXXX) are tracked for this project. The milestone's functional requirements were defined by the slice-level success criteria and "After this" demos in the roadmap, all of which were met as documented in the success criteria results above.

## Deviations

S01: Added @types/node and test-setup.ts not in plan; Use This button wired in T03 instead of T04. S02: Used vitest-canvas-mock instead of jest-canvas-mock (crashes in Vitest). S03: Added text case to AlignmentBar.tsx toBoundingRect (not in plan, required for exhaustive switch); used single onConvertToPath callback instead of separate add/remove.

## Follow-ups

Production CORS configuration should be added when deployment is set up (M003). SVG sanitization should be considered if untrusted SVG input becomes possible. Convert-to-paths produces ImageObject with Blob URL — a first-class path object type could be added later. Multi-line text-to-path conversion treats input as single line. Canvas components lack dedicated unit tests (verified via TypeScript compilation only).
