#+TITLE: Widget Component Generation — Spec #+AUTHOR: Craig Jennings #+DATE: 2026-07-12 #+TODO: TODO | DONE #+TODO: DRAFT READY DOING | IMPLEMENTED SUPERSEDED CANCELLED * DRAFT Widget component generation :PROPERTIES: :ID: 3ac0d42c-db1a-4d21-bce4-e63785fef0ba :END: - 2026-07-12 Sun @ 10:12:06 -0500 — drafted. ** Prototype iterations Per =claude-rules/ui-prototyping.md=, a non-trivial-UI spec carries its prototype evidence here. This spec's UI evidence is the [[file:../prototypes/panel-widget-gallery.html][panel widget gallery]] itself — the living catalogue is the prototype set, not a numbered iteration series: - *Research:* reference photos of period hardware (Berna 3 studio suite, aviation clusters, broadcast consoles, pedals, marine/telegraph instruments) filed in [[file:../../working/retro-stereo-widgets/references/][working/retro-stereo-widgets/references/]]; in-chat-pasted references have provenance noted in card notes and the 2026-07-11/12 session archive. - *Iterations:* the gallery's git history is the iteration record — N-series base widgets, then reference-derived R01–R31 built and behaviorally verified card by card (commits 7fff507 through 52a43ec, 2026-07-12). 84 cards total, every one CDP-verified (drag tracking, click response, animation, zero console errors). - *Final:* the gallery as of 52a43ec is the settled visual + behavioral spec each component port is judged against. * Metadata | Status | draft | |----------+-----------------------------------| | Owner | Craig Jennings | |----------+-----------------------------------| | Reviewer | (spec-review pending) | |----------+-----------------------------------| | Related | [[file:../../todo.org][todo.org — Retro widget catalogue]] | |----------+-----------------------------------| * Summary The panel widget gallery (84 behaviorally-verified retro-instrument cards) is a spec, not yet a library. This document defines how gallery cards become reusable components for three real targets — web (vanilla JS, the gallery itself as first consumer), Emacs (svg.el), and waybar (GTK CSS) — driven by actual demand rather than porting all 84. * Problem / Context The gallery grew card by card as a visual and behavioral catalogue: each card is a self-contained inline builder function inside one 2258-line HTML file. That was right for the collection phase — fast iteration, one canvas, one review surface. It is wrong for consumption: a waybar panel, an Emacs game, or a web UI that wants a VU meter today would have to copy-paste from the gallery's internals and hand-fork the styling. The groundwork is already laid. Tokens are single-sourced in [[file:../prototypes/tokens.json][tokens.json]]; =gen_tokens.py= emits the gallery's =:root= CSS block, [[file:../prototypes/tokens-waybar.css][tokens-waybar.css]], and [[file:../prototypes/gallery-tokens.el][gallery-tokens.el]] (27 tests, 100% coverage). An svg.el proof widget ([[file:../prototypes/gallery-widget.el][gallery-widget.el]], the needle gauge, 10 ERT tests plus an rsvg-convert side-by-side render) proved the Emacs pipeline — the riskiest unknown — end to end. What's missing is the middle layer: parameterized, importable widgets per target, and a decision procedure for which widgets earn a port. * Goals and Non-Goals ** Goals - A consumer in any of the three targets can use a ported widget without reading the gallery's source. - The gallery stays the single visual/behavioral spec; ports are judged against it, and the web port *is* what the gallery runs. - Porting effort tracks demand: a widget is ported when a named consumer wants it, not because it exists. - Every port carries tests in its target's idiom (CDP behavioral for web, ERT + visual harness for Emacs, panel test suite for waybar). ** Non-Goals - Porting all 84 cards. Most cards are catalogue entries; a port with no consumer is inventory nobody asked for. - Shared rendering code across targets. Reuse lives in shared tokens and shared SVG geometry math, not shared code — each target renders in its own idiom (recorded architecture decision). - Framework wrappers (React or otherwise) ahead of a real framework consumer. - Level-2/3 codegen (widget-spec → renderer generation) in this spec's v1 — that's the Phase 5 decision point, deliberately after hand-porting experience exists. ** Scope tiers - v1: demand inventory, web library extraction, Emacs ports of the demanded widgets, one waybar pilot panel, the Level-2 generator go/no-go. - Out of scope: new widget collection (R-series closed), shared cross-target rendering code, interaction parity beyond each target's native idiom. - vNext (log to todo.org at hand-off): React/framework wrappers, Level-2/3 codegen if Phase 5 says go, ports beyond the demand matrix as consumers appear. * Design ** For a consumer Web: include =widgets.js= (classic script) after the token =:root= block; call a builder — e.g. =GW.vuMeter(el, {value: 0.6})= — and get back a handle with setters. The gallery page itself becomes a grid of exactly these calls, so the demo and the library cannot drift. Emacs: =(require 'gallery-widgets)= (the =gallery-widget.el= pattern, one file per widget family or one shared file — Phase 3 sizes this); each widget is a function from parameters to an svg.el object, tokens resolved through =gallery-tokens.el=. Interaction (keymap, click regions) is per-widget Emacs idiom, not a translation of pointer drags. Waybar: no widget functions — waybar consumes =tokens-waybar.css= plus the banked composition idioms (lit/unlit key states, LED rows, engraved captions). The pilot restyles one real panel (audio, in =~/.dotfiles=) so its GTK CSS reads from the generated token sheet; "component" at this target means a documented CSS pattern, because GTK CSS has no scripting surface. ** For an implementer Three layers, already agreed: tokens (values) → widget spec (the gallery card: geometry, params, behavior) → per-target renderers. The card is the contract. A port re-reads the card's builder for geometry constants and behavioral rules (detent snapping, VU ballistics via the =vuDb()= law, exclusive-select), then re-expresses them natively. Geometry math (polar helpers, scale interpolation tables like =EDGE_SCALE=, =SEG14_MAP=) is copied deliberately per target and cross-checked by tests, not shared as a runtime dependency — the false-coupling judgment from the token work applies to code too. Extraction order inside Phase 2: the card builders already share =svgEl=/=polar= helpers and document-scoped gradients/filters; lifting them means (a) moving each demanded builder into =widgets.js= behind a parameter object, (b) replacing its gallery card with a call, (c) re-running the existing CDP behavioral probes unchanged — the probes drive the rendered DOM, so a green re-run is the no-regression proof. * Alternatives Considered ** Port everything to every target up front - Good, because the catalogue would be uniformly consumable. - Bad, because ~84 × 3 ports is months of work with no consumer for most of it, and unconsumed ports rot silently. - Bad, because it front-loads the Level-2 codegen question before hand-port experience exists to answer it. ** Shared renderer core (one geometry engine, thin target adapters) - Good, because geometry fixes would land once. - Bad, because the targets' rendering models genuinely differ (DOM+CSS animation vs static svg.el re-render vs GTK CSS with no scripting); an abstraction spanning them is speculative and was already rejected in the recorded architecture decision. - Neutral, because shared *values* (tokens) and shared *math* (cross-checked constants) capture most of the benefit without the coupling. ** Skip the web extraction; keep the gallery monolithic and port only outward - Good, because it's less work now. - Bad, because the gallery-as-demo property is lost: web consumers copy-paste internals, and the spec and any web usage drift apart immediately. - Bad, because the extraction is cheap insurance — the CDP tests already exist to catch regressions. * Decisions [7/8] ** DONE SVG-first substrate - Context: curved dials, needles, glow filters must render identically enough across web and Emacs; librsvg reproduces gradients and blur filters. - Decision: widgets with curved/needle geometry are authored as SVG in every scripted target; plain-CSS forms stay CSS only where the target is CSS-native (waybar). - Consequences: easier — one geometry language, rsvg side-by-side verification works; harder — some pure-CSS gallery cards need SVG re-expression when ported to Emacs. - Resolved live with Craig through the gallery/svg.el work (2026-07-11/12); proven by the needle-gauge slice (b137223). ** DONE Single token source - Context: the amber retune required a 30-site find/replace before tokenization. - Decision: =tokens.json= is the only place a design value is authored; =gen_tokens.py= emits all three target artifacts; generated blocks carry markers and are never hand-edited. - Consequences: easier — one-line retunes, drift impossible; harder — adding a token means touching the generator (tested, 100% coverage). - Resolved and shipped (4408779). ** DONE Per-target interaction layers - Context: pointer drag has no Emacs equivalent; GTK CSS has no scripting. - Decision: interaction is designed per target in its native idiom; only the widget's *state model* (positions, ranges, detents, exclusivity) is common, carried by the card. - Consequences: easier — each target feels native; harder — behavioral parity is a per-port judgment, tested per target rather than shared. - Resolved live with Craig (2026-07-11). ** DONE Demand-driven porting - Context: 84 cards, three targets, most cards have no consumer today. - Decision: a widget is ported when the demand matrix names a real consumer (a waybar panel, an Emacs game/UI, a web surface); the matrix is Phase 1's deliverable and Craig approves it. - Consequences: easier — effort lands where it's used; harder — the catalogue stays partially ported indefinitely, by design. - Agreed in the 2026-07-12 outline. ** DONE Gallery-as-spec - Context: the ui-prototyping rule wants working prototypes as design evidence. - Decision: the gallery is the canonical visual + behavioral spec; every port is judged against its card, and the web library's demo *is* the gallery. - Consequences: easier — no separate spec artifact to maintain; harder — gallery regressions are spec regressions, so its CDP checks are load-bearing. - Agreed in the 2026-07-12 outline; prototype evidence under the status heading. ** DONE Level-1 codegen only, for now - Context: token codegen shipped; widget-level codegen (Level 2/3) is speculative before hand-port experience. - Decision: defer the Level-2 go/no-go to Phase 5, after roughly five hand ports. - Consequences: easier — no premature generator abstraction; harder — early ports carry hand-duplicated geometry constants until the decision point. - Recorded architecture decision (2026-07-11 session). ** DONE Waybar pilot is the audio panel - Context: the pilot should be a real panel with active development and a full test suite. - Decision: the audio panel (=~/.dotfiles=) is the waybar pilot; archsetup owns the dotfiles work end to end per the standing rule in =.ai/notes.org=. - Consequences: easier — freshest panel, known suite; harder — visual changes to a daily-driver panel need the manual-testing checklist. - Agreed in the 2026-07-12 outline. ** TODO Web library packaging - Owner / by-when: Craig / at spec-review. - Context: the gallery is a single self-contained =file://= HTML page; ES modules don't load over =file://=, and a React consumer doesn't exist yet. - Decision (proposed): =widgets.js= ships as a classic script exposing one namespace object (working name =GW=), loaded by the gallery via a relative =