Architecture

How YAML becomes an interactive diagram — the compile pipeline, stage by stage.

Marrow is a compiler with a browser runtime attached. Text goes in, a validated model comes out, geometry is computed and checked, and two renderers turn it into either SVG or canvas inside a self-contained page.

The pipeline

  YAML source
      │
      ▼
  ┌─────────┐  parse (line/col preserved) → strict validation → normalize
  │ schema  │  errors carry code, path, line, col, snippet, hint, suggestions
  └─────────┘
      │  Topology  (the normalized model every module speaks)
      ▼
  ┌─────────┐  classify  → one of six archetypes
  │ layout  │  size      → measure nodes, labels included (deterministic tables)
  │         │  place     → ELK layered · compose mosaic · polar · circle
  │         │  route     → libavoid orthogonal, or polylines for radial
  │         │  tidy      → coincident runs, gutter buses, draw order
  └─────────┘
      │  Scene  (boxes + polylines — the geometric contract)
      ▼
  ┌─────────┐  L1 fails the build · L2 auto-repairs · L3 advises
  │  lint   │
  └─────────┘
      │
      ▼
  ┌─────────────┐         ┌──────────┐
  │ render-svg  │  hero   │  canvas  │  scale
  │             │  tier   │  engine  │  tier
  └─────────────┘         └──────────┘
      │
      ▼
  ┌─────────┐  collapse → plan pages → compile
  │  site   │  single self-contained file, or a multi-page site
  └─────────┘
      │
      ▼
  HTML  +  the ~20 KB runtime client (camera, inspector, spotlight, minimap, export)

Stage by stage

schema — text to model

One validation code path. YAML is parsed with positions preserved so every diagnostic can point at an exact line and column. Validation is strict: unknown keys are errors, x- prefixed keys are preserved and ignored. Output is the normalized Topology — the single in-memory shape every downstream module consumes.

layout — model to geometry

Classify picks one of six archetypes; declared structure (zones, tiers) always outranks inferred shape. Size measures each node at the exact pixel size and letter-spacing the renderers use, via static glyph tables in @marrow/metrics — measurement must be deterministic and identical across machines, which rules out asking a browser.

Place runs the engine that suits the archetype: ELK layered for zoned, tiered, and flat; a compose pass to pack zone mosaics; polar for hub-spoke; circular for rings and meshes. Radial presets allocate arc space from actual node widths, so they're overlap-free by construction.

Route uses libavoid for orthogonal routing, polylines for radial shapes (orthogonal channels look wrong on a ring).

Tidy applies cable-tray discipline: merge same-pair parallels onto one visible stroke, pull fan-outs onto shared gutter lanes, order draws so alarms sit above healthy runs. On a real HQ zone this took 165 links down to 36 unique geometries.

scene — the contract

Boxes and polylines, plus geometry helpers. Everything downstream speaks this and nothing else. Port chips are scene geometry, placed collision-free by the layout stage; renderers draw them verbatim rather than inventing positions.

lint — the gate

The geometry linter runs before anything renders. L1 defects fail the build: node overlap on label-inclusive boxes, edge segments through foreign node interiors, endpoints not touching their node's box, port-chip overlap. L2 auto-repairs; L3 advises.

render-svg and canvas-engine — geometry to pixels

Two renderers, one visual language. SVG is the hero tier — full fidelity, crisp at any zoom, SVG export. The canvas host is the scale tier, used when the node count would make SVG unusable. Every visual change must land in both, plus the shared stylesheet.

Shared conventions worth knowing before touching either:

  • Corner-anchored indicators. Status LED and badge sit centered on the chip's corner arc at 45°, offset r·(1−1/√2). Both get a 2 px background halo ring.
  • Background-halo knockout. Anything an edge may cross — node labels, zone titles — uses paint-order: stroke; stroke: var(--bg) in SVG and strokeText on canvas, and sits in a layer above the edges. A crossing link is knocked out cleanly instead of slicing through letters.
  • Measure what you draw. SVG pins labels with textLength; canvas condenses (never stretches) using a cached measureText.

site — pages and packaging

Collapse decides what's summarized. The planner decides single file versus multi-page site and which pages exist. The compiler inlines styles, the runtime, and the icon sprites actually used. Page builds are content-hashed and memoized, so a rebuild only touches pages whose inputs changed.

runtime — the client

About 20 KB: camera, inspector, spotlight, minimap, search, export, command palette. The chrome is shadcn/ui ported to vanilla CSS and TypeScript — never installed as components, because output must stay dependency-free.

server and cli

The dev server watches, rebuilds, and pushes live reloads over SSE, with a small JSON API. The CLI is the user-facing surface: validate, build, serve, snapshot, import, icons, schema.

Design invariants

These are the properties the whole design serves. Breaking one is a bug, not a trade-off:

  1. Determinism. Same input, byte-identical output.
  2. Self-containment. No network requests when a diagram opens.
  3. Declared meaning. Nothing is inferred from names.
  4. Machine-readable diagnostics. Every error has a stable code and a fix hint.
  5. Overlap-free geometry. Enforced by the linter, not by hope.