Skip to content

Architecture

This page condenses ADR 0005 (docs/decisions/0005-hand-drawn-canvas-renderer.md). The ADR holds the reasoning and the benchmarks.

Layer Where Job
Sketch @draw/sketch Turn a polyline or a polygon, a roughness and a seed into rough strokes.
Paint @draw/sketch Turn a Shape into a list of draw operations. Planned.
Canvas the app Replay the operations on a canvas, own the camera loop and measure text.
  • Pure. An exported function returns the same output for the same input. A seeded generator holds mutable state only inside one call and is never exported.
  • Plain data out. Results are readonly and serializable, with points in frame-local space as LocalPoint.
  • Seeded. A Seed from @draw/shapes fixes every random choice, so the wobble is stable across frames, reloads and machines.
  • No runtime dependency except @draw/shapes, for its branded types.

Straight-edged outlines (rectangle, diamond, arrowheads, line polylines) are roughened edge by edge. Smooth outlines (ellipse, arrow arc) are resampled to a few points and a jittered smooth curve is drawn through them. Roughening a 64-point ellipse edge by edge reads as a polygon, so the caller says which kind of outline it passes.

Fills follow the stroke’s base points. A curve returns the jittered points it was drawn through, so a later hachure stays inside the drawn outline.

Sketch type-checks against the source of @draw/shapes, not its build. tsconfig.json maps @draw/shapes to ../shapes/src/index.ts, so vp check and vp test see a shapes change at once, with no build in between.

The package build uses tsconfig.build.json instead, which has no such mapping. With the mapping, the declaration build treats the shapes sources as part of its program and writes a .d.ts file next to each of them in packages/shapes/src. Without it, the build leaves @draw/shapes as an external import. isolatedDeclarations lets the declaration build emit the types without reading the shapes package, so the build doesn’t depend on shapes being built first.