1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
|
#+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 [8/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.
** DONE Web library packaging
- 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: =widgets.js= ships as a classic script exposing one namespace object (working name =GW=), loaded by the gallery via a relative =<script src>=; the shared helpers (=svgEl=, =polar=, =vuDb=, scale tables) move inside it. Framework wrappers are vNext, demand-gated.
- Consequences: easier — gallery keeps working from =file://=, zero build tooling; harder — no tree-shaking or import isolation (acceptable at this scale).
- Approved by Craig 2026-07-12 (componentization go-ahead, option 1); build gated on his per-card validation pass (lamps in the gallery masthead cards).
* Implementation phases
** Phase 1 — Demand inventory
Build the widget-to-target matrix from real consumers: walk the live waybar panels (net/bt/audio/maint), the Emacs surfaces Craig names (games, dashboards), and any web UI, and record which gallery cards each actually wants. Deliverable: a short matrix table in this spec's appendix, approved by Craig. Tree untouched.
** Phase 2 — Web library extraction
Lift the demanded cards' builders into =docs/prototypes/widgets.js= behind parameter objects; the gallery becomes calls into the library. Green re-run of the existing CDP behavioral probes is the no-regression gate. Each extraction batch leaves the gallery fully working.
** Phase 3 — Emacs ports
Extend the =gallery-widget.el= pattern for the demanded widgets: per-widget ERT (token resolution, geometry math normal/boundary/error, SVG structure, state-tracks-value) plus the rsvg-convert side-by-side visual harness against the gallery card. Keymap/click-region interaction designed per widget. Wired into =make test-elisp=.
** Phase 4 — Waybar pilot (audio panel)
Restyle the audio panel's GTK CSS onto =tokens-waybar.css= plus the banked composition idioms. Lives in =~/.dotfiles=; archsetup drives edit/test/commit/push end to end and drops the dotfiles inbox note. Visual result goes on the manual-testing checklist (daily-driver panel).
** Phase 5 — Generator decision point
After ~5 hand ports, revisit Level-2 codegen with evidence: how much of each port was mechanical duplication vs judgment. Deliverable: a go/no-go recorded as a decision here (go spawns its own spec). Tree untouched.
* Acceptance criteria
- [ ] Demand matrix exists in the appendix and Craig has approved it.
- [ ] Every demanded web widget is a =widgets.js= call in the gallery; CDP probes green; gallery renders unchanged from =file://=.
- [ ] Every demanded Emacs widget has ERT coverage and an rsvg side-by-side render matching its card; =make test-unit= green.
- [ ] The audio panel reads its palette from =tokens-waybar.css=; panel suite green; manual-testing checklist entry filed.
- [ ] Phase 5 go/no-go recorded as a dated decision.
* Readiness dimensions
Answer each, or write "N/A because…".
- Data model & ownership: =tokens.json= user-authored; =:root= block, =tokens-waybar.css=, =gallery-tokens.el= generated (marker-fenced, never hand-edited); =widgets.js= and Emacs widget files hand-authored; the gallery HTML owns card layout and demo calls.
- Errors, empty states & failure: builders validate parameter ranges and clamp (the gallery's existing behavior — e.g. no-cross flag clamps, detent wrap); Emacs widgets signal =user-error= on out-of-range tokens/params (the =gallery-widget-token= missing-token error is the pattern).
- Security & privacy: N/A because everything is local rendering of design assets; no credentials, no network.
- Observability: web — CDP probes plus zero-console-error checks; Emacs — ERT output; waybar — the panel's existing smoke/AT-SPI harness.
- Performance & scale: N/A beyond the existing gallery scale (84 cards render fine); ports are per-widget, no aggregate load.
- Reuse & lost opportunities: tokens and geometry constants reused by value with cross-checking tests; svg.el, librsvg, and GTK CSS used as the platforms provide them; deliberate non-reuse of cross-target code recorded above.
- Architecture fit & weak points: fits the shipped 3-layer architecture; weak point is spec drift between a card and its ports — mitigated by the side-by-side harness and by the gallery-as-demo property on web.
- Config surface: no new knobs; token values remain the only tuning surface.
- Documentation plan: =docs/prototypes/README.org= gains a consumers section (how to use =widgets.js= / the Emacs widgets / the waybar tokens); no user-facing docs beyond that.
- Dev tooling: existing =make test-unit= / =make test-elisp= cover new suites (tests auto-discovered under =tests/gallery-*=); =gen_tokens.py= unchanged; no new targets expected.
- Rollout, compatibility & rollback: web/Emacs are additive files; the audio-panel restyle is the one shared-state change — it rides the dotfiles repo, reverts with =git revert=, and changes styling only (no behavior).
- External APIs & deps: rsvg-convert and svg.el already proven by the slice; waybar GTK CSS subset already exercised by the live panels; no unverified assumptions.
* Risks, Rabbit Holes, and Drawbacks
- Pure-CSS cards demanded into Emacs need SVG re-expression — sized per widget in Phase 3; if one balloons, it goes back to the demand matrix rather than stalling the phase.
- The gallery extraction could tempt a rewrite-everything pass; the phase is scoped to *demanded* builders only, monolith remainder stays put.
- GTK CSS renders differently from the browser (no SVG filters, different gradient behavior); the pilot may reveal idioms that don't translate — acceptable, the pilot exists to find them, and findings feed the composition-idiom notes rather than blocking.
- Hand-duplicated geometry constants can drift before Phase 5; each port's tests pin its constants against the card's values.
* References / Appendix
- Architecture + banked variant/composition ledger: 2026-07-11/12 session archive (=.ai/sessions/=).
- Token generator: [[file:../prototypes/gen_tokens.py][gen_tokens.py]], tests in =tests/gallery-tokens/=.
- Emacs slice: [[file:../prototypes/gallery-widget.el][gallery-widget.el]], tests in =tests/gallery-widgets/=.
- Demand matrix: (Phase 1 deliverable — lands here.)
* Review and iteration history
** 2026-07-12 Sun @ 10:12:06 -0500 — Craig Jennings — author
- What: initial draft.
- Why: collection phase closed at R01–R31; porting needs a decision procedure and per-target shape before any consumer work starts.
- Artifacts: [[file:../prototypes/panel-widget-gallery.html][panel-widget-gallery.html]], todo.org "Retro widget catalogue" (DOING).
|