docs: capture playground redesign intent from _rewound snapshot
Source: archive/playground-redesign-2026-snapshot branch (commit 287e3a1). Reference-only — playground is being rewritten in SolidJS, so the redesign's implementation will not be merged, but its design intent is preserved here for the rewrite team.
This commit is contained in:
parent
d3373a4e44
commit
01f6522fd5
1 changed files with 340 additions and 0 deletions
340
docs/redesign/playground-2026.md
Normal file
340
docs/redesign/playground-2026.md
Normal file
|
|
@ -0,0 +1,340 @@
|
|||
# Playground Redesign — 2026 (Design Intent)
|
||||
|
||||
> **Purpose of this doc.** Captures design intent of an unfinished playground UI
|
||||
> redesign that lived as uncommitted changes in a `MEMLNaut-NISPS_rewound`
|
||||
> working copy. The implementation will not be merged into the current vanilla
|
||||
> playground — the playground is being rewritten in SolidJS — but the *design*
|
||||
> shouldn't be lost. Read this as a feature spec for the SolidJS rewrite, not as
|
||||
> a description of current code.
|
||||
>
|
||||
> **Source snapshot.** `archive/playground-redesign-2026-snapshot` branch in
|
||||
> this repo. Single commit `287e3a1` on top of `ddb6b77`. Three files:
|
||||
> `playground/a-immersive.html`, `playground/css/a-immersive.css`,
|
||||
> `playground/js/a-app.js`. ~1200 lines net of changes.
|
||||
>
|
||||
> **Status.** Reference-only. Do not merge into the vanilla playground — those
|
||||
> files have moved on independently in `main` and a textual merge would be
|
||||
> meaningless.
|
||||
|
||||
---
|
||||
|
||||
## Context
|
||||
|
||||
The pre-redesign playground UI was organised around a collapsible **bottom
|
||||
sheet** containing a sticky **floating bar** (mode toggles, train/randomize
|
||||
buttons, follow toggle, expand chevron). The sheet would expand upward to show
|
||||
synth/visual controls. RL thumbs-up/down floating buttons sat just above the
|
||||
collapsed sheet at `bottom: 92px`.
|
||||
|
||||
The redesign moves to a **right-side macOS-style dock + drawer system** and
|
||||
makes the **top heatmap strip directly interactive**. It also adds **undo** for
|
||||
RL actions and a **per-parameter override system** that unifies how visual-mode
|
||||
and synth-mode parameters can be constrained.
|
||||
|
||||
There are five themes, summarised here in decreasing order of architectural
|
||||
weight.
|
||||
|
||||
---
|
||||
|
||||
## Theme 1 — Right-side dock + drawer system
|
||||
|
||||
### What changed
|
||||
|
||||
Replaces the bottom-sheet pattern with a vertical dock pinned to the right edge
|
||||
of the viewport, plus a stack of named drawers that open to the left of the
|
||||
dock when their dock icon is activated.
|
||||
|
||||
- **Removed:** `<div class="bottom-sheet collapsed">`, `<div class="floating-bar">`,
|
||||
the sheet expand/collapse chevron, sheet-internal sticky mode bar, sheet
|
||||
status line.
|
||||
- **Removed JS:** `wireBottomSheet()`, `$bottomSheet`, `$sheetContent`,
|
||||
`$floatingBar`, `$chevronBtn`, `sheetExpanded` state.
|
||||
- **Added:** `<div class="dock" id="dock">` with five icon buttons:
|
||||
- `data-drawer="training"` — Training (Add Example, Train, Clear Ex, Clear
|
||||
All, Randomize, plus preset chips: Calm/Chaos, Rainbow, Vortex, Spiral,
|
||||
Embers; loss-canvas section)
|
||||
- `data-drawer="mode"` — Input/Output mode toggles (was the floating-bar
|
||||
pill toggles: Joystick/Hands and Visual/Synth)
|
||||
- `data-drawer="synth"` — Synth controls
|
||||
- `data-drawer="params"` — Engine parameters (see Theme 4)
|
||||
- `data-drawer="help"` — Help
|
||||
- **Added:** `<div class="drawer-stack" id="drawer-stack">` containing one
|
||||
`<div class="drawer hidden" data-drawer="…">` per icon, each with a
|
||||
`drawer-header` (title + close ×) and `drawer-body`.
|
||||
- **Added JS:** `wireDock()` to hook up icon clicks and drawer close buttons.
|
||||
|
||||
### Inferred intent
|
||||
|
||||
- Reclaim canvas real estate. The bottom-sheet ate vertical space whenever
|
||||
expanded; a side dock keeps controls flush to one edge and only one drawer
|
||||
opens at a time.
|
||||
- Surface mode controls (Visual/Synth, Joystick/Hands) without forcing the
|
||||
user to expand the sheet. Each function lives behind one click on a labeled
|
||||
icon.
|
||||
- macOS-dock visual idiom is a familiar metaphor — users intuit
|
||||
"click icon → panel appears."
|
||||
- The action-row inside Training drawer (Add Example / Train / Clear Ex /
|
||||
Clear All / Randomize) is grouped by purpose rather than by toolbar real
|
||||
estate constraints, so destructive/dim variants (`.action-btn.dim`) read
|
||||
as secondary.
|
||||
|
||||
### Key UX flow
|
||||
|
||||
1. Canvas is fullscreen by default, no chrome over it except top heatmap, RL
|
||||
buttons, joystick, and the right dock.
|
||||
2. User clicks dock icon → corresponding drawer slides in from the right
|
||||
(between dock and canvas), other drawers close.
|
||||
3. User clicks ✕ on drawer header *or* the same dock icon again → drawer
|
||||
closes, canvas is unobstructed.
|
||||
4. Mutually exclusive drawers (only one open at a time) keeps the visual
|
||||
hierarchy simple.
|
||||
|
||||
### Implementation notes for SolidJS rewrite
|
||||
|
||||
- This is the largest theme and pulls HTML, CSS, and JS together. In SolidJS
|
||||
it should naturally become a `<Dock>` component listing icon → drawer
|
||||
pairs, plus a single `<DrawerStack>` rendering whichever is active from a
|
||||
signal.
|
||||
- CSS custom properties `--dock-width: 48px` and `--dock-gap: 8px` are
|
||||
introduced; layout (joystick, hand-pip, dev-panel) is offset by these.
|
||||
- Old positioning constants — joystick & hand-pip at `bottom: 136px`, RL
|
||||
buttons at `bottom: 92px` — were tied to the collapsed bottom sheet's
|
||||
88px height. With no sheet, they shift down (joystick/hand-pip to 100px,
|
||||
RL buttons to 36px). The rewrite should drive these from layout, not
|
||||
hardcode.
|
||||
- `dev-panel` shifts right edge from `right: 16px` to
|
||||
`right: calc(var(--dock-width) + 24px)` to clear the dock.
|
||||
|
||||
---
|
||||
|
||||
## Theme 2 — Interactive heatmap strip
|
||||
|
||||
### What changed
|
||||
|
||||
The top heatmap strip — previously read-only visualization of model outputs —
|
||||
becomes the primary parameter input surface.
|
||||
|
||||
### Two interactions per cell
|
||||
|
||||
1. **Drag** (pointermove with motion threshold > 3px) horizontally across a
|
||||
cell → directly sets that parameter's value to the normalized x-position
|
||||
within the cell.
|
||||
2. **Click** (pointerup with no motion) → toggles a per-parameter popup with
|
||||
override controls (min, max, curve, freeze).
|
||||
|
||||
### Tooltip
|
||||
|
||||
Existing tooltip extended:
|
||||
- During hover (no drag): shows `{name}: {value}` with a `▾` indicator hinting
|
||||
the bar is clickable.
|
||||
- During drag: stays visible and updates live as user scrubs.
|
||||
- New CSS: `.heatmap-cell.dragging` with `cursor: ew-resize`,
|
||||
`.heatmap-cell-bar { pointer-events: none }` so child bar doesn't intercept
|
||||
parent pointer events.
|
||||
|
||||
### Popup contents
|
||||
|
||||
Per-parameter popup (`#param-popup` or similar) built on demand for the
|
||||
clicked param:
|
||||
- min slider (range 0–1)
|
||||
- max slider (range 0–1)
|
||||
- curve slider (range 0–1, see Theme 4)
|
||||
- freeze toggle — when frozen, dragging the heatmap bar updates a *fixed
|
||||
value* directly rather than going through the model
|
||||
- close button / clicks-outside dismissal
|
||||
- show/hide handled with `popupHideTimer` (300ms grace period to let user
|
||||
move pointer from heatmap cell into the popup without dismiss-on-leave)
|
||||
|
||||
### New JS
|
||||
|
||||
- `setHeatmapValue(paramIndex, e, cell)` — converts pointer x to normalized
|
||||
value, applies override semantics (writes to `ov.fixedValue` if frozen),
|
||||
routes outputs, syncs UI.
|
||||
- `showParamPopup(paramIndex, cell)` — positions popup near the cell.
|
||||
- `hideParamPopup()` — clears active popup state.
|
||||
- `wireParamPopup()` — wires popup controls to update underlying override.
|
||||
- Tracking globals: `activePopupParam`, `popupHideTimer`.
|
||||
|
||||
### Inferred intent
|
||||
|
||||
- Make the heatmap a control surface, not just a readout. Saves a trip to
|
||||
the bottom-sheet/drawer for common adjustments.
|
||||
- Per-parameter freeze enables hybrid workflow: freeze a few params at fixed
|
||||
values while letting the model drive the rest.
|
||||
- Popup is in-context (anchored to the bar) rather than in a side panel,
|
||||
reducing eye travel.
|
||||
|
||||
### Key UX flow
|
||||
|
||||
- **Quick adjust:** point at heatmap bar → drag horizontally → value changes
|
||||
live, model output reroutes immediately.
|
||||
- **Override setup:** click bar → popup opens → adjust min/max to constrain
|
||||
range, or freeze at fixed value, or shape curve (see Theme 4) → click
|
||||
outside or wait for cell pointer-leave to dismiss.
|
||||
|
||||
### Notes
|
||||
|
||||
- `.heatmap-cell-frozen` CSS class dims frozen cells visually so user knows
|
||||
what's manually overridden.
|
||||
- Drag detection uses 3px motion threshold to disambiguate click-vs-drag.
|
||||
- Pointer capture (`setPointerCapture`) ensures drag continues even if
|
||||
pointer leaves the cell.
|
||||
|
||||
---
|
||||
|
||||
## Theme 3 — Undo
|
||||
|
||||
### What changed
|
||||
|
||||
Adds an undo button below the RL thumbs-up/down buttons, with a 20-step
|
||||
history of weight snapshots taken before each RL action.
|
||||
|
||||
### Implementation
|
||||
|
||||
- New HTML: `<button class="rl-undo-btn" id="btn-undo">` in the RL buttons
|
||||
cluster, with an SVG curl-arrow icon.
|
||||
- New CSS: `.rl-undo-btn` (28px circle, dim-by-default at opacity 0.4,
|
||||
brightens to 0.8 on hover, 0.7 when undo is available),
|
||||
`.rl-undo-btn.has-undo` and `.rl-undo-btn.flash` states. Animation
|
||||
`rl-flash` (0.25s ease-out) for feedback after undo.
|
||||
- Layout: `.rl-buttons` becomes `display: grid` with two columns and two
|
||||
rows; the undo button spans both columns in row 2 (`grid-column: 1 / -1`)
|
||||
and is centered.
|
||||
- New JS:
|
||||
- `const undoStack = []` and `const MAX_UNDO = 20`
|
||||
- `pushUndoSnapshot()` — called before each RL action; clones current
|
||||
weights into the stack; pops oldest if over MAX_UNDO.
|
||||
- `onUndo()` — pops last snapshot, restores weights, flashes button.
|
||||
- `updateUndoButton()` — toggles `.has-undo` class based on
|
||||
`undoStack.length > 0`.
|
||||
|
||||
### Inferred intent
|
||||
|
||||
- RL actions in this playground are exploratory and easy to "go too far" —
|
||||
e.g. a thumbs-down sequence that drives the model into uninteresting
|
||||
territory. Without undo, user has to retrain or randomize, losing all
|
||||
history.
|
||||
- Visual subtlety (low default opacity, only fully visible when there's
|
||||
something to undo) keeps it out of the way until needed.
|
||||
|
||||
### Key UX flow
|
||||
|
||||
User clicks 👎 → push snapshot, apply update. Result is bad. User clicks
|
||||
↶ → restores previous weights. Button flashes briefly to confirm.
|
||||
|
||||
### Notes
|
||||
|
||||
- This is the cleanest standalone theme — minimal coupling to the rest of
|
||||
the redesign. Consider implementing first in the SolidJS rewrite to
|
||||
validate the snapshot model.
|
||||
|
||||
---
|
||||
|
||||
## Theme 4 — Per-parameter override generalization
|
||||
|
||||
### What changed
|
||||
|
||||
Generalizes the existing `groupOverrides` system (which let synth-mode params
|
||||
be constrained per-group) to also work in visual mode, and unifies access
|
||||
behind a single `getParamOverride(i)` accessor.
|
||||
|
||||
### New shapes
|
||||
|
||||
- `const visualOverrides = VISUAL_PARAM_NAMES.map(() => ({ min: 0, max: 1, curve: 0.5, frozen: false, fixedValue: 0.5 }))`
|
||||
— one override entry per visual parameter, parallel to the existing
|
||||
per-section/per-param structure on the synth side.
|
||||
- `getParamOverride(paramIndex)` — given a heatmap index, returns an object
|
||||
with the same shape regardless of mode:
|
||||
- In `visual` mode: returns the corresponding `visualOverrides[i]` entry
|
||||
directly.
|
||||
- In `synth` mode: looks up `paramToSection[paramIndex]` and returns a
|
||||
proxy onto `groupOverrides[si].params[li]`, with `muted` mapped to
|
||||
`frozen` so the popup UI can be mode-agnostic.
|
||||
|
||||
### Renames (cosmetic but indicative)
|
||||
|
||||
- `buildRawParams()` → `buildEngineParams()`
|
||||
- `$rawParams` → `$engineParams`
|
||||
- HTML id `raw-params` → `engine-params`
|
||||
|
||||
### New JS helpers
|
||||
|
||||
- `syncEngineParams()` — pull current values into the drawer-rendered slider
|
||||
list.
|
||||
- `formatEngineVal(v, step)` — display formatting for slider readouts.
|
||||
|
||||
### Other change
|
||||
|
||||
- `let rlExplorationDecay = 0.97` (was `const`) — runtime-tunable, presumably
|
||||
exposed via an Engine drawer slider.
|
||||
|
||||
### Inferred intent
|
||||
|
||||
- Visual mode previously had no way to clamp/freeze individual parameters.
|
||||
Synth mode did. The asymmetry forced the heatmap popup to behave
|
||||
differently per mode, which is poor UX.
|
||||
- Unifying behind one accessor lets Theme 2's popup code be mode-agnostic.
|
||||
- "Engine" terminology generalizes "raw params" to whichever output system
|
||||
is active.
|
||||
|
||||
### Notes
|
||||
|
||||
- This theme is **coupled to Theme 2** — the popup is what consumes the
|
||||
override accessor. They should ship together (or, in the SolidJS rewrite,
|
||||
be designed together as one feature: "per-parameter constraints + the UI
|
||||
to set them").
|
||||
|
||||
---
|
||||
|
||||
## Theme 5 — Polish (positioning, status line)
|
||||
|
||||
Small adjustments forced by the larger redesign or independent quality-of-life
|
||||
fixes:
|
||||
|
||||
- **Status line** moves from inside the sheet (`<div class="sheet-status">`)
|
||||
to a free-floating pill anchored at `bottom: 8px`, centered, low-opacity
|
||||
(`#status-line`). Lives outside any drawer; visible always.
|
||||
- **Joystick & hand-pip** move from `bottom: 136px` to `bottom: 100px`
|
||||
(sheet no longer takes up vertical space).
|
||||
- **RL buttons** move from `bottom: 92px` to `bottom: 36px`, gap from 32px
|
||||
to 12px, layout from flex to 2-col grid (to accommodate the spanning undo
|
||||
button below).
|
||||
- **`rlExplorationDecay`** changes from `const` to `let` (see Theme 4).
|
||||
- **Heatmap tooltip** gets `▾` suffix to hint clickability and stays visible
|
||||
during drag.
|
||||
- **Hand-pip video** loses `/* Hidden — only used as MediaPipe source */`
|
||||
comment on the position-absolute hidden video element. Behavior unchanged.
|
||||
|
||||
### Notes
|
||||
|
||||
- These are positioning constants tied to the shape of the surrounding
|
||||
layout. The SolidJS rewrite shouldn't carry them forward as literals —
|
||||
derive from layout / CSS variables.
|
||||
|
||||
---
|
||||
|
||||
## What this redesign does NOT change
|
||||
|
||||
For completeness — these survived the redesign untouched:
|
||||
|
||||
- Joystick container and joystick interaction logic (`wireJoystick`).
|
||||
- Hand tracker / MediaPipe pipeline (`createDevPanel(() => handTracker)`).
|
||||
- Synth visualizer canvas (`#synth-vis-canvas`, `SYNTH_SECTIONS`).
|
||||
- Loss canvas drawing.
|
||||
- Help overlay (`wireHelp`).
|
||||
- Group overrides for synth mode (extended in Theme 4, but base mechanism
|
||||
unchanged).
|
||||
- Gamepad input.
|
||||
- Output mode routing logic (`routeOutputs`, `rawParamValues`).
|
||||
|
||||
---
|
||||
|
||||
## Summary table for the SolidJS rewrite
|
||||
|
||||
| Theme | Carry forward? | How it should land in SolidJS |
|
||||
|---|---|---|
|
||||
| 1. Dock + drawers | Yes | `<Dock>` + `<DrawerStack>` driven by signal for active drawer |
|
||||
| 2. Interactive heatmap | Yes | Heatmap cells as components with pointer-event handlers; popup as a portal anchored to clicked cell |
|
||||
| 3. Undo | Yes | Single store with `undo()` action + size cap; trivially reactive |
|
||||
| 4. Param override generalization | Yes | One unified store keyed by param index; mode is just a derived accessor |
|
||||
| 5. Polish | Re-derive | Don't carry positioning literals; recompute from layout |
|
||||
Loading…
Reference in a new issue