aboutsummaryrefslogtreecommitdiff
path: root/docs/specs/2026-07-12-component-generation-spec.org
blob: 95c471290dca5344272ea9a040ec7b374574111b (plain)
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
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
#+TITLE: Widget Component Generation — Spec
#+AUTHOR: Craig Jennings
#+DATE: 2026-07-12
#+TODO: TODO | DONE
#+TODO: DRAFT READY DOING | IMPLEMENTED SUPERSEDED CANCELLED

* DOING Widget component generation
:PROPERTIES:
:ID:       3ac0d42c-db1a-4d21-bce4-e63785fef0ba
:END:
- 2026-07-12 Sun @ 20:57:50 -0500 — READY → DOING: phases decomposed into todo.org tasks under "Retro widget catalogue" (=:SPEC_ID:= stamped); Phase 1 extraction starts now per the option-1 approval.
- 2026-07-12 Sun @ 20:57:50 -0500 — DRAFT → READY: spec-review passed (Ready; 3 findings — the option-1 supersession, the card-record refactor, stale counts — all accepted and folded same pass).
- 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–R56 built and behaviorally verified card by card (7fff507 through the gallery-upgrades squash merge bc93388 and beyond, 2026-07-12). 109 cards as of the 2026-07-12 review, every one CDP-verified (drag tracking, click response, animation, zero console errors); the committed probe suite lives in =tests/gallery-probes/=.
- *Final:* the gallery at current =main= is the settled visual + behavioral spec each component port is judged against — it keeps growing, so a port is judged against its card as it stands, not a frozen SHA.

* Metadata
| Status   | doing                             |
|----------+-----------------------------------|
| Owner    | Craig Jennings                    |
|----------+-----------------------------------|
| Reviewer | Claude Code (archsetup)           |
|----------+-----------------------------------|
| Related  | [[file:../../todo.org][todo.org — Retro widget catalogue]] |
|----------+-----------------------------------|

* Summary
The panel widget gallery (109 behaviorally-verified retro-instrument cards as of the 2026-07-12 review) 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). The web library is extracted whole (a lossless transform of code that already exists); the Emacs and waybar ports are driven by actual demand rather than porting the full catalogue.

* 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 ~4300-line HTML file (as of the 2026-07-12 review; it still grows). 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
- Emacs/waybar ports of the full catalogue. Most cards are catalogue entries; a scripted port with no consumer is inventory nobody asked for. (The web extraction is exempt — it transforms code that already exists.)
- 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: full web library extraction (with the card-record refactor), demand inventory for the scripted targets, Emacs ports of the demanded widgets, one waybar pilot panel, the Level-2 generator go/no-go.
- Out of scope: new widget collection as part of this work (the catalogue keeps growing on its own track — new cards land as =GW.*= calls once extracted), 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 1: the card builders already share =svgEl=/=polar=/=vuDb=/drag helpers and document-scoped gradients/filters; lifting them means (a) moving the shared engine and each builder into =widgets.js= behind a parameter object, (b) replacing its gallery card with a declarative card record (no / title / stage builder / options / readout / note / spec sheet incl. reference link / validation lamp) rendered by one =card()= path, (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. Batches of roughly 10-15 widgets keep each commit reviewable and the gallery working throughout.

* Alternatives Considered
** Port everything to every target up front
- Good, because the catalogue would be uniformly consumable.
- Bad, because ~109 × 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 (Emacs and waybar targets)
- Context: 109 cards (as of 2026-07-12 review), three targets, most cards have no Emacs or waybar consumer today.
- Decision: an *Emacs or waybar* port happens when the demand matrix names a real consumer (a waybar panel, an Emacs game/UI); the matrix is Phase 1's deliverable and Craig approves it. The *web* extraction is exempt: it is a lossless transform of code that already exists, so it covers the full catalogue (option-1 approval, 2026-07-12).
- Consequences: easier — port effort lands where it's used, and the web library is complete from day one; harder — the Emacs/waybar surface stays partially ported indefinitely, by design.
- Agreed in the 2026-07-12 outline; web-extraction exemption approved with the option-1 go-ahead (2026-07-12).

** 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). The extraction itself is ungated — a lossless transform verified by the CDP probes; Craig's per-card validation pass (the status lamps) gates only the per-widget Emacs ports and the final blessing.

* Review findings [3/3]
** DONE Extraction scope and gate superseded by the option-1 approval :blocking:
The spec's Phase 1 → Phase 2 order gates the web extraction on the demand matrix, and the Web-library-packaging decision says the build is "gated on his per-card validation pass." Craig's componentization go-ahead (option 1, 2026-07-12 ~21:30) superseded both: the web extraction covers the *full* catalogue as a lossless transform and starts immediately; the demand matrix and the validation lamps gate only the per-widget Emacs ports and the final blessing. An implementer reading the spec as written would follow the retired order. Recommended change: re-scope the Demand-driven-porting decision to the Emacs/waybar targets, drop the extraction gate from the packaging decision, and reorder/reword Phases 1-2.
Disposition: accepted — pre-agreed by Craig (recorded in todo.org and the 2026-07-12 session log). Folded into Decisions 4 and 8 and Phases 1-2.
** DONE Card-as-component refactor absent from Phase 2
The approved build includes restructuring each gallery card into a declarative record (no/title/stage builder/options/readout/note/spec-sheet incl. reference link/validation lamp) so the page becomes data plus =GW.*= calls, but Phase 2 as written only covers lifting builders into =widgets.js=. Without it the "gallery becomes calls into the library" claim leaves the card chrome hand-rolled per card. Recommended change: name the card-record refactor as part of Phase 2's deliverable.
Disposition: accepted — folded into the Design section and Phase 2.
** DONE Stale gallery facts
The spec states 84 cards, a 2258-line file, the R-series closed at R31, and 52a43ec as the final SHA. The gallery has since grown to 109 cards (R-series through R56, ~4300 lines) across the gallery-upgrades merge and the takuzu-survey builds. Not a design problem, but stale numbers in the contract mislead the implementer sizing the extraction. Recommended change: refresh the counts and phrase them as of the review date, since the catalogue still grows.
Disposition: accepted — counts refreshed and dated throughout.

* Implementation phases
** Phase 1 — Web library extraction
Lift *all* card builders into =docs/prototypes/widgets.js= behind parameter objects (classic script, =GW= namespace, shared helpers inside), and restructure each gallery card as a declarative record (no / title / stage builder / options / readout / note / spec sheet incl. reference link / validation lamp) so the page becomes data plus =GW.*= calls. Batched (roughly 10-15 widgets per batch); a green re-run of the CDP behavioral probes (=tests/gallery-probes/=) is the per-batch no-regression gate, and each batch leaves the gallery fully working from =file://=. Ungated by the validation lamps (option-1 approval — lossless transform).

** Phase 2 — Demand inventory (Emacs / waybar)
Build the widget-to-target matrix for the *scripted-port* targets: walk the live waybar panels (net/bt/audio/maint) and the Emacs surfaces Craig names (games, dashboards), and record which gallery cards each actually wants. Deliverable: a short matrix table in this spec's appendix, approved by Craig. Tree untouched. Runs in parallel with or after Phase 1 — the extraction does not wait on it.

** 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 gallery card is a declarative record calling into =widgets.js=; 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 (109 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 redesign pass; the phase is a *lossless transform* only — builders move and cards become records, but no widget's look or behavior changes (the probes enforce it).
- 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 @ 20:57:50 -0500 — Claude Code (archsetup) — reviewer + responder
- What: full spec-review (code read against the live gallery, probes re-baselined green at 753380e). Three findings, all accepted and folded: the option-1 approval superseded the demand-gated extraction order (Phases 1-2 swapped and re-scoped, Decisions 4 and 8 reworded), the card-record refactor named as part of the extraction phase, stale counts refreshed (84→109 cards, R31→R56). Rubric: Ready. Flipped READY, then DOING with the phase decomposition into todo.org.
- Why: Craig's componentization go-ahead (option 1, 2026-07-12 ~21:30) landed after drafting — extraction is a lossless transform and proceeds ungated; the validation lamps gate only Emacs ports and the final blessing. The spec had to match the approved build before tasks decomposed from it.
- Artifacts: the three completed findings under =* Review findings=; probe repair commit 753380e; todo.org build tasks under "Retro widget catalogue" (=:SPEC_ID:= 3ac0d42c-db1a-4d21-bce4-e63785fef0ba).

** 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).