# S02 — Design Canvas Core (View 2) — Research

**Date:** 2026-03-26

## Summary

S02 builds View 2, the Konva.js-powered 2D design canvas. This is a moderately complex slice with well-documented technology (react-konva has excellent examples for every feature needed), a clear handoff from S01 (SVG string + TraceMetadata in App.tsx state), and five requirements to satisfy: R013 (artboard shapes), R015 (basic shapes with selection/resize), R016 (object/layer panel), R017 (alignment tools), R018 (undo/redo).

The core risk is state management complexity — the canvas needs a central store of typed objects with undo/redo across all mutations (add, move, resize, reorder, delete, property changes). react-konva's `Transformer` component handles selection handles and resize/rotate natively; multi-select uses `tr.nodes([...])`. The official Konva docs provide React examples for every interaction pattern needed (Transformer, multi-select with rubber-band, drag-and-drop reordering, undo/redo via history stack, dash patterns for line styles, z-index layering). No custom rendering engine needed.

The imported SVG from View 1 should be rendered via `Konva.Image` from a Blob URL — this is simpler and more reliable than parsing individual SVG paths, preserves visual fidelity, and keeps the vector as a single moveable/scaleable/rotatable object on canvas. Text objects are deferred to S03. Shield and pennant artboard shapes are custom SVG path outlines rendered as Konva.Path clip masks.

## Recommendation

**Build in this order: state model → artboard → Konva stage with imported SVG → shape tools + selection → object panel → alignment → undo/redo.**

Use a single `useCanvasState` hook (or a context + reducer) as the central state store. All canvas objects live in an array with typed discriminated unions (`type: 'rect' | 'circle' | 'ellipse' | 'line' | 'imported-svg'`). Every mutation goes through a dispatch function that records a snapshot for undo/redo. The Konva `Stage` renders objects from this store; `Transformer` handles interactive resize/rotate. The object panel reads and reorders the same store.

Install `konva` and `react-konva` as dependencies. No other new libraries needed — alignment math and undo/redo are simple enough to implement inline. Konva.js handles all 2D rendering, hit detection, drag, and transform.

## Implementation Landscape

### Key Files (existing)

- `app/src/App.tsx` — ViewState routing (`import | canvas | export`). Currently stores `_svgResult` (string) and `_traceMetadata` (TraceMetadata) via `handleUseThis`. The `view === 'canvas'` branch renders a placeholder div that S02 replaces with `<DesignCanvas svgData={svgResult} metadata={traceMetadata} />`.
- `app/src/types/engine.ts` — TraceMetadata interface (consumed by canvas for metadata display).
- `app/src/App.css` — Global component styles. S02 adds canvas toolbar, object panel, and artboard setup styles here.
- `app/src/index.css` — CSS variables (--accent, --border, --bg, --text-h, etc.) and base typography. Canvas UI must use these.
- `app/vite.config.ts` — Vite + React + Vitest config. No changes needed.
- `app/src/test-setup.ts` — Vitest setup with jest-dom matchers. May need `canvas` mock setup for Konva in jsdom.

### Key Files (new)

- `app/src/types/canvas.ts` — Canvas object type definitions: `CanvasObject` discriminated union (`RectObject | CircleObject | EllipseObject | LineObject | ImportedSvgObject`), `ArtboardConfig` (shape + dimensions + units), `CanvasState` (objects array, selectedIds, artboard config, history stack).
- `app/src/hooks/useCanvasState.ts` — Central state management hook. Manages: object CRUD, selection (single + multi), artboard config, undo/redo history. Returns state + dispatch functions. History stored as `useRef<CanvasState[]>` with a step pointer (same pattern as Konva's official undo/redo example — `useRef` avoids unnecessary re-renders on history push).
- `app/src/views/DesignCanvas.tsx` — View 2 container. Layout: top toolbar, left canvas area with Konva Stage, right object panel. Receives `svgData` and `metadata` props from App.tsx.
- `app/src/views/DesignCanvas.module.css` — CSS module for canvas layout (follows P008 pattern).
- `app/src/components/canvas/ArtboardSetup.tsx` — Modal/overlay shown on first entering canvas. Shape picker (rect, square, circle, oval, shield, pennant, custom), width/height inputs, units toggle (inches/mm). Produces `ArtboardConfig`.
- `app/src/components/canvas/KonvaStage.tsx` — Wraps `<Stage>` + `<Layer>`. Renders artboard background shape, all canvas objects, selection `Transformer`, rubber-band selection rectangle. Handles click-to-select, shift-click multi-select, click-on-empty to deselect.
- `app/src/components/canvas/CanvasToolbar.tsx` — Top toolbar: select tool, shape tools (rect, circle, ellipse, line), zoom controls, undo/redo buttons, grid toggle.
- `app/src/components/canvas/ObjectPanel.tsx` — Right sidebar: lists objects by layer order (top = front), drag-to-reorder, visibility toggle (eye icon), lock toggle (lock icon), click-to-select, double-click-to-rename.
- `app/src/components/canvas/AlignmentBar.tsx` — Appears when objects selected: align left/center/right/top/middle/bottom, distribute H/V, center on artboard.
- `app/src/components/canvas/ShapeProperties.tsx` — Property panel for selected shape: stroke color, stroke weight, fill toggle, line style (for line objects), dimensions display.
- `app/src/utils/artboardShapes.ts` — SVG path data for non-primitive artboard shapes (shield, pennant). Also artboard dimension presets. Pure functions, easily testable.
- `app/src/utils/alignment.ts` — Alignment calculation functions: align, distribute, center-on-artboard. Pure math, easily testable.

### Build Order

**1. Foundation — types + state hook + artboard setup (highest priority, unblocks everything)**
- Define `CanvasObject` types in `canvas.ts`
- Build `useCanvasState` hook with add/remove/update/select/deselect/reorder + undo/redo
- Build `ArtboardSetup` component for shape/dimension selection
- This is the riskiest piece — state shape affects every downstream component. Get it right first.

**2. Konva Stage + imported SVG rendering**
- Install `konva` and `react-konva`
- Build `KonvaStage` with artboard background rendering
- Wire imported SVG from S01 as a `Konva.Image` (convert SVG string → Blob URL → Image)
- Add selection via `Transformer`, single + multi-select, deselect on empty click
- Wire `DesignCanvas` view into App.tsx (replace placeholder)

**3. Shape tools + selection interaction**
- Add shape creation tools (rect, circle, ellipse, line)
- Wire toolbar buttons for tool switching
- Implement rubber-band selection rectangle for multi-select
- Line style variants (solid, dashed, dotted, double, centerline via dash arrays)

**4. Object panel + alignment**
- Build `ObjectPanel` with layer list, reorder, visibility, lock
- Build `AlignmentBar` with align/distribute/center operations
- Wire both to `useCanvasState`

**5. Undo/redo + keyboard shortcuts**
- Wire Ctrl+Z / Ctrl+Shift+Z to undo/redo
- Undo/redo buttons in toolbar
- Delete key removes selected objects

### Verification Approach

- `cd app && npx vitest run --reporter=verbose` — all tests pass (existing 23 + new)
- `cd app && npx tsc --noEmit` — zero TypeScript errors
- Unit tests for pure functions: `alignment.ts` (align calculations), `artboardShapes.ts` (path data generation), `useCanvasState` (add/remove/update/select/undo/redo state transitions)
- Component tests: `ArtboardSetup` (shape selection produces correct config), `ObjectPanel` (renders object list, reorder dispatches correct action), `AlignmentBar` (button clicks dispatch alignment)
- Konva rendering tests may require mocking `canvas` in jsdom — `jest-canvas-mock` or `vitest-canvas-mock` package, or test pure state logic separately from rendering
- Manual visual verification: start dev server (`cd app && npm run dev`), navigate to canvas, create artboard, add shapes, verify selection handles, undo/redo

## Don't Hand-Roll

| Problem | Existing Solution | Why Use It |
|---------|------------------|------------|
| 2D canvas rendering + hit detection | `konva` + `react-konva` | Official React bindings for Konva.js. Handles all canvas rendering, event handling, drag, transform. Well-documented with React examples for every pattern we need. |
| Selection handles (resize/rotate) | Konva `Transformer` component | Built-in to react-konva. Handles resize handles, rotation, multi-node selection, min-size constraints via `boundBoxFunc`. |
| Multi-select rubber-band | Konva selection rectangle pattern | Official example uses `selectionRectangle.getClientRect()` + `Konva.Util.haveIntersection()` — exact pattern documented in Konva site. |

## Constraints

- **jsdom has no Canvas API** — Konva requires `<canvas>`. Tests that import react-konva components will fail without a canvas mock (`jest-canvas-mock` or `vitest-canvas-mock`). Structure tests to isolate pure state logic (testable without canvas) from Konva rendering (needs mock or integration test).
- **noUnusedLocals + noUnusedParameters strict** — tsconfig.app.json has these enabled. All variables and params must be used or prefixed with `_`. S01 already uses `_svgResult` and `_traceMetadata` in App.tsx — S02 removes the underscores when these are consumed.
- **`#root` has `width: 1126px` and `border-inline`** — Canvas view needs to either work within this container or override it. Canvas should probably fill available space. Consider using `width: 100%` or adjusting root styles for the canvas view.
- **SVG from View 1 is a raw string** — To render on Konva canvas, convert to Blob URL → Image element → `Konva.Image`. Cannot use `dangerouslySetInnerHTML` in canvas context.
- **CSS modules for view layout, global App.css for shared components** (P008) — Follow this pattern for DesignCanvas.module.css and shared toolbar/panel styles.

## Common Pitfalls

- **Transformer scale vs dimensions** — When a shape is resized via Transformer, Konva applies `scaleX/scaleY` rather than changing `width/height`. On `onTransformEnd`, reset scale to 1 and multiply into width/height to keep the state model clean. This is documented in every Konva Transformer example.
- **Undo/redo with refs vs state** — Store history in `useRef` (not `useState`) to avoid re-renders on every history push. Only the "current state" triggers re-renders. The official Konva undo/redo example uses this pattern.
- **Multi-select Transformer attachment** — Must call `transformerRef.current.nodes([...nodeRefs])` in a `useEffect` when selection changes, then `getLayer().batchDraw()`. Forgetting `batchDraw` causes stale handles.
- **Konva z-index is render order** — In react-konva, rendering order in JSX determines z-index. To reorder layers, reorder the objects array in state. Don't use Konva's imperative `moveToTop()/moveToBottom()` — let React's declarative rendering handle it.
- **Artboard clip region** — If objects extend beyond the artboard, they should still be visible during design but clipped on export. Use Konva `Group` with `clipFunc` for the artboard boundary, or simply render the artboard outline without clipping during design.

## Open Risks

- **Canvas mock stability in Vitest** — `vitest-canvas-mock` or `jest-canvas-mock` may not fully cover Konva's canvas usage. If canvas mocking proves unreliable, fall back to testing only pure state/logic functions and defer Konva component tests to integration testing. This affects test coverage but not functionality.
- **Performance with many objects** — A design with 50+ objects plus an imported SVG image could slow down Konva rendering. Mitigate by using `listening: false` on non-interactive elements (artboard background), and `perfectDrawEnabled: false` for complex shapes. Not expected to be an issue for typical sign/patch designs (5-20 objects).
- **Shield/pennant artboard shapes** — These are custom clip paths. Need to define SVG path data that looks right at arbitrary aspect ratios. Shield is straightforward (symmetrical arch-top shape). Pennant is a triangle/banner shape. If aspect ratio distortion is problematic, constrain proportions.

## Sources

- Konva.js Transformer + multi-select: official docs (context7: /konvajs/site) — selection rectangle, shift-click, `tr.nodes([...])`
- Konva.js undo/redo: official docs (context7: /konvajs/site) — `useRef` history pattern with step pointer
- react-konva Transformer component: official docs (context7: /konvajs/react-konva) — `onTransformEnd` scale reset pattern
- Konva.js dash arrays for line styles: official docs — `line.dash([10, 5])` for dashed, `[10, 20, 0.001, 20]` for dot-dash
- Konva.js SVG on canvas: official docs — `Konva.Image.fromURL()` for SVG rendering, `Konva.Path` for individual SVG paths
- Konva.js layering: official docs — render order in JSX determines z-index, `zIndex()` for imperative control