ADR-0006 — Schema-driven artifact config panel with localStorage persistence
Status: Accepted (documents the existing decision)
Context
Artifacts are parameterized systems (leg counts, forces, colors). Exposing parameters to visitors turns viewers into players — a core memorability lever — and gives the author a live tuning tool during development.
Problem
Give every artifact tunable controls without every artifact hand-building forms, and persist visitor tweaks without a backend.
Decision
A declarative schema (ArtifactConfigSchema: typed fields, groups, defaults, schema
version) drives a generic floating panel (ArtifactConfigPanel →
ArtifactConfigForm → shared inputs). useArtifactConfig persists state per artifact
in localStorage under artifact:<id>:config:v<version>, merging stored values against
the current schema so field additions/removals degrade gracefully; bumping version
abandons stale state wholesale.
Alternatives considered
- leva / tweakpane / dat.gui: mature, but React-bound (leva), visually foreign (dat.gui), and all would fight the site’s styling; the panel is also a branding surface.
- Hand-rolled controls per artifact: n× the work, n× the inconsistency.
- URL-only state (no localStorage): shareable but forgetful; the roadmap adds URL on top (permalinks, artifacts.md R4) rather than instead.
Tradeoffs
- (+) New artifact controls = one schema object; persistence and grouping are free.
- (+) Version key gives a clean migration story (abandonment is a migration strategy at this scale).
- (−) Stringly typed:
config: Record<string, any>— no compile-time link between schema and usage (code-quality R5 plans a generic fix). - (−) Panel discoverability is poor (collapsed gear); no URL sharing yet;
isDraggableprop is advertised but unimplemented (delete list). - (−) No validation on read: corrupt/hand-edited localStorage JSON can throw and brick the artifact page (code-quality R7 adds guards).
Consequences
- The schema is becoming artifact metadata in its own right: the index preview mode, config permalinks, and OG variations will all read it. Keep it serializable and UI-agnostic.
- Shared input components (
Slider,ColorInput, …) are the seed of the site’s design system — style them once, on brand (branding R1/R6), and everything inherits.
Future evolution
- URL permalinks (
?c=) layered over localStorage (artifacts.md R4). - Typed generic schema (code-quality R5).
- Possible extraction as a Vue package — future-ideas.md, maintenance-tax warning applies.