accepted

Core compute returns semantic regions, not raw drawing primitives

Every `compute*` function returns a model with named semantic regions — `headerStats`, `scaleBar`, `plot`, `legend`, `emptyState` — not a flat list of shapes. Layout policy lives in compute; renderers paint regions by name. New renderers (canvas, Figma, static export) plug in without re-deriving layout.

architecturecore-contractapi

Context

Two candidate architectures for a chart library:

  • Draw-list core — compute returns a flat list of SVG primitives (<path>, <circle>, <text>). Renderers paint the list. Simplest possible API, but every consumer re-derives layout; a Figma plugin can’t reposition the legend without parsing shapes; accessibility roles (header, plot, legend) are lost.

  • Semantic regions — compute returns a structured model: headerStats, scaleBar, plot, legend, emptyState. Renderers map regions to outputs. Layout policy is centralised; accessibility and cross-renderer parity are free; new renderers drop in.

Decision

Adopt semantic regions as the ComponentModel<TPlot, TLegend> contract. Every chart returns the same top-level shape, even if regions are null. React renderers paint by region name. Future renderers (static export, Figma plugin, canvas) consume the same shape. Layout is a compute concern, not a renderer concern.

Consequences

  • Static export composition (e.g. a scouting report PDF with multiple charts) can position regions from the model without rendering first.
  • Consolidated legends across multiple charts become a composition-layer concern, not a per-chart hack — all legend regions share the same contract.
  • Renderers are thin. Adding a canvas renderer doesn’t require re-writing compute; it needs one region → canvas mapping.
  • New regions (e.g. tooltip, brushControls) extend the model type; renderers unaware of them drop them silently, without crashing.
  • Empty state is a first-class region, not a render-layer branch — the empty shell decision (empty-state-shell-preserved) composes cleanly with this.
← All decisions