diff options
Diffstat (limited to 'docs/prototypes/README.org')
| -rw-r--r-- | docs/prototypes/README.org | 120 |
1 files changed, 118 insertions, 2 deletions
diff --git a/docs/prototypes/README.org b/docs/prototypes/README.org index 9df85cb..a4f591f 100644 --- a/docs/prototypes/README.org +++ b/docs/prototypes/README.org @@ -15,7 +15,123 @@ point at. affordance for the NETWORKS/NEARBY headers (busy-style throbber + list fade). - [[file:2026-07-03-sound-panel-prototype.html][2026-07-03-sound-panel-prototype.html]] — the audio/pulsemixer console; layout reference for [[file:../specs/2026-07-03-audio-panel-spec.org][the audio-panel spec]]. -- [[file:2026-07-03-panel-widget-gallery-prototype.html][2026-07-03-panel-widget-gallery-prototype.html]] — the shared instrument-console - widget kit (lamps, engraved sections, console keys, needle gauges). +- [[file:panel-widget-gallery.html][panel-widget-gallery.html]] — The Dupre Kit: the shared instrument-console + gallery (lamps, engraved sections, console keys, needle gauges). The + living catalogue: every instrument here is the visual + behavioral spec for + its reusable-component ports. All instruments build from [[file:widgets.js][widgets.js]] (below); + the gallery page is the reference consumer. - [[file:2026-07-03-waybar-redesign-prototype.html][2026-07-03-waybar-redesign-prototype.html]] — three directions for sprucing up waybar in the dupre instrument-console aesthetic (future work). + +* The Dupre Kit ([[file:widgets.js][widgets.js]]) + +The whole kit lives in =widgets.js= — a classic script (no modules, no build +step) exposing the =DUPRE= namespace with one builder per gallery card. Load it +with =<script src="widgets.js"></script>= and call builders directly. + +** Builder contract + +=DUPRE.name(host, opts)= → handle ={el, get, set, ...}=. =host= is an empty +element the instrument renders into. =opts.onChange(value, text)= fires on every +state change, including the initial paint; =text= is the instrument's canonical +readout string. (Exception: live meters whose original init was silent — they +paint on the first =set=/=push=.) + +** Styling + +Instrument-internal CSS ships inside =widgets.js= (the =DUPRE_CSS= block, injected +once as =<style id="dupre-css">= at load), so instruments are fully styled on any +page. The design tokens (=--gold=, =--glow-hi=, the amber family, =--mono=, +=--pulse-rate=, …) must be defined on =:root= by the consumer — the gallery's +generated =:root= block is the reference; =tokens.json= is the source. + +** Tick contract (live meters) + +The page owns the clock and the signal source; builders own rendering state. +Live meters expose value-driven handles that repaint synchronously and fire +=onChange=: + +- =set(level)= for level meters (peak-hold logic lives in the builder) +- =push(v)= / =set(samples, current)= for history meters (the builder owns + the ring buffer) +- =set(samples, amp)= for sampled traces (samples normalized) + +Instrument-owned animation (the R17 scope trace, R31 radar sweep, R44 servo +chase, R52 pseudo-PC, R53 day clock, R10 TIME page) runs inside the builder, +gated on =prefers-reduced-motion=. + +** Keyboard contract (instruments that take keys) + +Sibling of the tick contract above, and the same split: focus is an ambient +resource like the clock — exactly one thing holds it across the whole page — +so *the target owns focus and delivery, the builder declares what it accepts*. +Neither half works alone. Only the page knows which instrument is live; only the +instrument knows that Backspace means DEL. + +*The builder declares* a =KEYS= table on itself, mapping a canonical key name +to the argument its handle's =press= takes: + +: DUPRE.abcKeypad.KEYS = { A:'A', ..., '0':'0', ..., Space:'SPC', Backspace:'DEL', Enter:'ENT' } + +One declaration, wired natively per target: + +- *Web* — the builder attaches a =keydown= listener *to its own focusable + element* (=tabindex=0=, focused on click). =DUPRE.slideRule= is the precedent: + arrows step it, scoped to its own element. The browser's focus system does + the arbitration, which is why it doesn't fight the gallery's own global + Escape handler. +- *Emacs* — the SVG region is an image and never sees a keypress, so the mode + installs the same table into its keymap and calls =press=. This is why the + declaration is a table and not a function over a DOM event: a port that + can't read the instrument's intent has to re-derive it, and then the two drift. +- *waybar* — GTK focus, same shape. + +Rules, each of them a bug someone will otherwise ship: + +- *Never listen on =document= or =window=.* An instrument that does fights every + sibling and the page's own handlers; on the gallery, every keystroke + anywhere would type into whichever card bound last. +- *=preventDefault= only what has a default worth suppressing* — Space scrolls + the page, Backspace can navigate back. Nothing else. +- *Let Tab and Escape bubble.* Tab is how the page is navigable at all, and + the gallery's audit stepper owns Escape. An instrument that swallows either + breaks something it can't see. +- *=press= filters, it does not trust.* A handle that appends whatever it is + handed will cheerfully append "F1". The table is the allowlist. +- *Click and key must land in the same place* — both route through =press=, so + the two paths cannot drift apart or fire different events. + +An instrument with no =KEYS= table takes no keys, which is most of them: the kit is +click-first and that is what makes it port. Keys are additive, and in Emacs +they are the *more* native idiom, not the less. + +** Style options + +Instruments with named style axes take them as constructor opts backed by a +=STYLES= table on the builder — e.g. =DUPRE.slideToggle(host, {onStyle:'green', +thumb:'brass'})= with the axes enumerated in =DUPRE.slideToggle.STYLES=, and +=handle.setStyle(axis, name)= to restyle a live instance. The gallery's +option chips are demo rigs driving =setStyle=. + +** Verification + +=tests/gallery-probes/= drives the full kit headlessly over CDP: +=probe.mjs= (every card renders + responds, size toggle, drags at zoom) and +=probe-fams.mjs= (screen-family retinting). Run with =node= against the +gallery file URL; both must pass before a gallery or widgets.js change lands. + +* Design tokens (single source, three targets) + +[[file:tokens.json][tokens.json]] is the source of truth for the design tokens (palette, amber +family, glows, pulse rate). [[file:gen_tokens.py][gen_tokens.py]] regenerates all three targets +(=python3 gen_tokens.py=): + +- the =:root= block inside the gallery HTML (web CSS custom properties) +- [[file:tokens-waybar.css][tokens-waybar.css]] — GTK =@define-color= declarations for the waybar panels +- [[file:gallery-tokens.el][gallery-tokens.el]] — an elisp alist for svg.el renderers + +[[file:gallery-widget.el][gallery-widget.el]] is the Emacs renderer (proof instrument: the needle gauge, +gallery card 10) — it reads gallery-tokens.el and emits the instrument as SVG via +svg.el, so the same look renders inside Emacs. Tests: +=tests/gallery-tokens/= (generator, unittest) and =tests/gallery-widgets/= +(renderer, ERT), both in =make test-unit=. |
