docs(phase-12): fold popover-hosted controls into spec + plan

Controls move from an inline per-page knob bar to a single popover-hosted
panel triggered by the lava-lamp icon, placed identically on every host
(Mix, Cut, Session, NowPlaying card). Dissolves the NowPlaying-controls
question — full parity via the popover. Adds the popover panel wave, panel
styling from theme tokens, and a popover-anchor open item.
This commit is contained in:
daniel-c-harvey
2026-06-17 06:07:49 -04:00
parent 76060f60a8
commit d6df0de63a
2 changed files with 340 additions and 177 deletions
+56 -27
View File
@@ -278,13 +278,27 @@ endpoint" — it is a content + upload + CMS + backfill + fetch slice (split int
**Three hosting modes of the one engine (Daniel corrected "backdrop").** *"backdrop?? MIXES doesn't really **Three hosting modes of the one engine (Daniel corrected "backdrop").** *"backdrop?? MIXES doesn't really
have a backdrop?"* — right: on Mix the visualizer is the full-bleed **centerpiece that IS the page**, not have a backdrop?"* — right: on Mix the visualizer is the full-bleed **centerpiece that IS the page**, not
something behind content. The one engine is hosted three ways (spec §3f): **mode A — visualizer-is-the-page** something behind content. The one engine is hosted three ways (spec §3f): **mode A — visualizer-is-the-page**
(Mix detail, full-bleed, seven-knob bar); **mode B — ambient environment** (Cut/Session detail, living (Mix detail, full-bleed centerpiece); **mode B — ambient environment** (Cut/Session detail, living
texture *behind* the hero+content — this is the only mode that is genuinely a "backdrop"); **mode C — texture *behind* the hero+content — this is the only mode that is genuinely a "backdrop"); **mode C —
contained live element** (NowPlaying card, a bounded live readout, `Fill`-sized to the card). Same engine, contained live element** (NowPlaying card, a bounded live readout, `Fill`-sized to the card). Same engine,
same datum contract — variance is entirely in hosting composition. **Controls (Daniel, full parity, §8b):** same datum contract — variance is entirely in hosting composition. **Controls (Daniel, full parity, §8b):**
the seven-knob bar + lava-lamp toggle ride **every Release Detail host** — Mix, Cut, **and** Session (modes the lava controls ride **every host** — Mix, Cut, Session, **and** the NowPlaying card — via the single
A and B both carry the controls, not Mix-only); only the contained NowPlaying card (mode C) suppresses them popover-hosted panel (below); controls are no longer a per-mode discriminator.
by default (one open sub-question — §8b-followup).
**Controls-hosting revision (Daniel, 2026-06-17 — supersedes the inline knob-bar model).** *"We have enough
[controls] now that I want to design a panel to be hosted in a popover for the visualizer controls. The
lava-lamp toggle should be wired to this popover, so anywhere we can put one Icon we can put the control
surface."* The eight knobs no longer ride an inline *bar* per page — they move into a **single
popover-hosted panel** triggered by the **lava-lamp icon** (click icon → panel pops over). This is **more
DRY than the per-page bar** (one `<icon → popover → panel>` composition reused verbatim, not three-to-four
per-host bar layouts) and it **dissolves §8b-followup**: with a popover, the small NowPlaying card places
the *same* icon as every other host and the panel floats on demand, so the "is the card too small for the
bar?" question evaporates — **full parity on all four surfaces, the popover way**. The SOLID seam: **one
panel component (`WaveformVisualizerControls` becomes the panel content), one popover host
(`WaveformVisualizerControlPopover`), placed by an icon anywhere.** Panel styled to the **NowPlaying Hero
look** — dark-navy ground, green-accent knobs, light icons, muted-navy filler — pulled from the
`deepdrft-tokens.css` source of truth (no hardcoded hex; spec §3g). New open item the popover creates: its
anchor/positioning per host (§8e) — a layout detail, not a presence decision.
**Deliverable 2 — NowPlayingHero overhaul (mode C).** `NowPlayingCard.razor` today animates **20 hardcoded **Deliverable 2 — NowPlayingHero overhaul (mode C).** `NowPlayingCard.razor` today animates **20 hardcoded
CSS-bounce bars** with no audio coupling (the "stochastic" visualizer). Replace them with the *same* CSS-bounce bars** with no audio coupling (the "stochastic" visualizer). Replace them with the *same*
@@ -299,13 +313,15 @@ etc.) — a `Mix`-named component on a Cut page is a lie that cements the wrong
**composition** (a new optional `Ambient` slot on `ReleaseDetailScaffold` for mode B; Mix keeps its own **composition** (a new optional `Ambient` slot on `ReleaseDetailScaffold` for mode B; Mix keeps its own
mode-A mount; the card is a mode-C contained mount; per-host control suppression), never a `switch (medium)` mode-A mount; the card is a mode-C contained mount; per-host control suppression), never a `switch (medium)`
in the engine (memory *One source, multiple views*; scaffold's "variance rides a slot, never a flag" idiom, in the engine (memory *One source, multiple views*; scaffold's "variance rides a slot, never a flag" idiom,
Phase 9 §5.3). The slot is named `Ambient` not `Backdrop` precisely because Mix doesn't use it. The lava Phase 9 §5.3). The slot is named `Ambient` not `Backdrop` precisely because Mix doesn't use it. **The lava
controls ride **every Release Detail host** (Mix, Cut, Session — full parity, Daniel's §8b call); only the controls are now one popover-hosted panel placed by the lava-lamp icon on every host** (Mix, Cut, Session,
contained NowPlaying card mounts controls-suppressed by default. The seam still supports flipping the card NowPlaying card — full parity, the popover dissolving the old card-suppression sub-question); the panel and
to controls-on later with no engine change (memory *Design for adaptability up front*). its NowPlaying-Hero styling are built once and reused (memory *Design for adaptability up front* — the
popover seam makes "place the controls anywhere there's an icon" a zero-cost composition).
Sequenced as **five waves**: `12.A → 12.B2(12.C ‖ 12.D)`, with **12.B1 a parallel server-side track** Sequenced as **six waves**: `12.A → {12.B1 → 12.B2, 12.E}`, then `(12.B2 ∧ 12.E) → (12.C ‖ 12.D)`
(`12.B1 → 12.B2`) that can start cold day one. **12.B1 a parallel server-side track** and **12.E (the popover controls panel) a third parallel track**,
both startable cold day one off the rename.
- **12.A — Rename to the abstraction (mechanical, no behavior change).** `Mix*``Waveform*` across the - **12.A — Rename to the abstraction (mechanical, no behavior change).** `Mix*``Waveform*` across the
five C#/Razor files + the TS module + its import path + the DI registration. **Load-bearing five C#/Razor files + the TS module + its import path + the DI registration. **Load-bearing
@@ -323,30 +339,43 @@ Sequenced as **five waves**: `12.A → 12.B2 → (12.C ‖ 12.D)`, with **12.B1
`EntryKey` and re-fetches on **track** change (not release change). **Depends on 12.A + 12.B1.** `EntryKey` and re-fetches on **track** change (not release change). **Depends on 12.A + 12.B1.**
Acceptance: Mix renders the same high-res lava via the track-cardinal fetch; a non-Mix track returns Acceptance: Mix renders the same high-res lava via the track-cardinal fetch; a non-Mix track returns
high-res. high-res.
- **12.E — Popover-hosted control panel (the controls revision).** Turn the renamed
`WaveformVisualizerControls` into the **panel content** and build `WaveformVisualizerControlPopover`
pairing the lava-lamp trigger icon with that panel as overlay content (`MudPopover`). Style the panel to
the **NowPlaying Hero look** from `deepdrft-tokens.css` (no hardcoded hex; spec §3g). Make the
state-scoping call (one shared `WaveformVisualizerControlState`). **Depends on 12.A only** — no per-track
datum needed, so runs **parallel to 12.B**. The unit every host then places. Acceptance: lava-lamp icon
opens a Hero-styled popover with all eight knobs; turning a knob drives the visualizer via the unchanged
`Changed` seam; one panel reused everywhere.
- **12.C — `Ambient` slot on `ReleaseDetailScaffold` + mount on detail pages (mode B, full parity).** - **12.C — `Ambient` slot on `ReleaseDetailScaffold` + mount on detail pages (mode B, full parity).**
Promote the full-bleed / foreground-stacking / dynamic-footer-clip pattern into the scaffold as an optional Promote the full-bleed / foreground-stacking / dynamic-footer-clip pattern into the scaffold as an optional
`Ambient` slot; Cut mounts the ambient layer **with the full seven-knob bar + lava-lamp toggle** (full `Ambient` slot; Cut mounts the ambient layer **and places the lava-lamp icon → popover** (full parity);
parity); Session mounts directly **also full-parity** (it doesn't compose the scaffold — spec §3e). Mix is Session mounts directly **also full-parity** (it doesn't compose the scaffold — spec §3e). Mix is
**unchanged** (mode A keeps its own mount + controls). Also makes the state-scoping call (recommend one **unchanged as a layer** (mode A keeps its own full-bleed mount); its only controls change is swapping the
shared `WaveformVisualizerControlState`). **Depends on 12.B2.** **§8b resolved (full parity) — no longer inline `TopRowCenter` bar for the lava-lamp icon → popover (12.E's affordance). **Depends on 12.B2 + 12.E.**
gated**; Cut and Session ship with both the ambient layer and the controls. **§8b resolved (full parity) — no longer gated**; Cut and Session ship with both the ambient layer and the
- **12.D — NowPlayingHero rewire (mode C).** Replace the synthetic bars with a contained, popover controls.
controls-suppressed `<WaveformVisualizer>` driven by the live cascaded player, pointed at the current - **12.D — NowPlayingHero rewire (mode C).** Replace the synthetic bars with a contained
track; add the `Fill`/container-sizing mode (spec §6c). **Depends on 12.A + 12.B2; independent of 12.C** `<WaveformVisualizer>` driven by the live cascaded player, pointed at the current track; add the
`Fill`/container-sizing mode (spec §6c); **place the lava-lamp icon → popover on the card** (full parity —
the popover dissolves the old suppression). **Depends on 12.A + 12.B2 + 12.E; independent of 12.C**
(different host). Acceptance: home card shows the real playing-track high-res waveform, at-rest when (different host). Acceptance: home card shows the real playing-track high-res waveform, at-rest when
nothing plays; no synthetic bars remain. nothing plays, and carries the lava-lamp icon → popover like every other host; no synthetic bars remain.
**Resolved by Daniel (2026-06-17), kept visible per file convention:** datum resolution → **Direction B** **Resolved by Daniel (2026-06-17), kept visible per file convention:** datum resolution → **Direction B**
(high-res all media; 512-bucket-fallback "Direction A" declined); multi-track-Cut datum → **dissolved by (high-res all media; 512-bucket-fallback "Direction A" declined); multi-track-Cut datum → **dissolved by
the per-track model** (renders the current track's datum, no album-representative choice); Cut/Session the per-track model** (renders the current track's datum, no album-representative choice); Cut/Session
hosting + controls → **full parity (option 3)**: all three hosting modes ship **and** the seven-knob bar + hosting + controls → **full parity (option 3)**: all three hosting modes ship **and** the lava controls ride
lava-lamp toggle ride every Release Detail host (Mix, Cut, Session), not Mix-only — the three-mode *layout* every host — the three-mode *layout* framing is retained, the change is that controls are no longer
framing is retained, the change is that controls are no longer Mix-suppressed on Cut/Session (the old "mode Mix-suppressed (the old "mode 1 Mix-only" and "controls Mix-only" alternatives are both closed);
1 Mix-only" and "controls Mix-only" alternatives are both closed). **Newly open (created by the full-parity **controls hosting → popover-hosted panel** (2026-06-17 revision): the controls move from an inline knob bar
flip + Direction B + per-track):** (a) **§8b-followup — do full-parity controls extend onto the NowPlaying to a single popover-hosted panel triggered by the lava-lamp icon, placed identically on every host;
home card (mode C)?** Full parity was answered against the *detail* pages; the small contained home card **§8b-followup is dissolved by this** — the NowPlaying card gets the icon → popover like everywhere else, so
stays **controls-suppressed by default** (a seven-knob bar on a compact now-playing card may be awkward) — full parity now spans all four surfaces (Mix, Cut, Session, NowPlaying card). **Open (created by the popover
recommend keeping it suppressed, but flag for Daniel; one-line composition flip in 12.D either way. revision + Direction B + per-track):** (a) **§8e — popover anchor/positioning per host**: where the
lava-lamp icon sits and the panel anchors on each host (Mix's `TopRightAction` corner is cleanest; the small
NowPlaying card is the tightest case and may look cramped) — recommend one popover with a per-host
`AnchorOrigin` parameter, not a fork; staff-engineer-owned layout call, flagged for a glance in review.
(b) **§8a-new — backfill shape + gate**: one-shot migration/script vs. a CMS (b) **§8a-new — backfill shape + gate**: one-shot migration/script vs. a CMS
batch action over the generalized generate action (recommend the CMS action; Daniel-gated to *run* either batch action over the generalized generate action (recommend the CMS action; Daniel-gated to *run* either
way; the fetch 404s gracefully for not-yet-backfilled tracks so it can ship before the backfill completes). way; the fetch 404s gracefully for not-yet-backfilled tracks so it can ship before the backfill completes).
@@ -3,17 +3,29 @@
Status: **design-complete, implementation-ready.** Daniel resolved the three §8 open questions on Status: **design-complete, implementation-ready.** Daniel resolved the three §8 open questions on
2026-06-17 — committing **Direction B** (high-res compute for all media), correcting the datum model to 2026-06-17 — committing **Direction B** (high-res compute for all media), correcting the datum model to
**per-track, not per-release**, and resolving §8b to **full parity on the lava controls**: the visualizer **per-track, not per-release**, and resolving §8b to **full parity on the lava controls**: the visualizer
rides every Release Detail host (Mix, Cut, Session) **and the seven-knob bar + lava-lamp toggle ride all rides every Release Detail host (Mix, Cut, Session) **and the controls ride all three** — not Mix-only. The
three** — not Mix-only. The three-hosting-mode *layout* framing (visualizer-is-the-page on Mix / ambient three-hosting-mode *layout* framing (visualizer-is-the-page on Mix / ambient environment on Cut/Session /
environment on Cut/Session / contained on NowPlaying) is retained; the change is that controls are now contained on NowPlaying) is retained; the change is that controls are present on all three detail-page hosts,
present on all three detail-page hosts, not suppressed on Cut/Session. The spec below is revised to those not suppressed on Cut/Session.
three. One small sub-question remains open (§8b-followup): whether full-parity controls also extend onto
the small NowPlaying home card (default: **no** — the card stays controls-suppressed). Author: **Controls-hosting revision (Daniel, 2026-06-17, supersedes the inline knob-bar model).** *"We have enough
product-designer. Date: 2026-06-17. **No code has been written by this doc.** [controls] now that I want to design a panel to be hosted in a popover for the visualizer controls. The
lava-lamp toggle should be wired to this popover, so anywhere we can put one Icon we can put the control
surface."* The eight knobs no longer ride an inline *bar* per page. They move into a **single
popover-hosted control panel** triggered by the **lava-lamp icon**: click the icon → the panel pops over.
This is **more DRY than the per-page inline bar** and it **dissolves §8b-followup** — with a popover, every
host (Mix, Cut, Session, *and* the NowPlaying home card) places the *same* lava-lamp icon and gets the
*identical* panel; full parity is achieved *through the popover*, not through a bar re-laid-out on each
page. §8b-followup is now **answered**: the NowPlaying card gets the icon → popover like everywhere else.
The panel adopts the **NowPlaying Hero look** (dark-navy ground, green-accent knobs, light icons, muted-navy
filler — §3g). The §3/§6 hosting sections and the wave decomposition (§7, controls work consolidates into a
distinct 12.E concern) are revised to this model. One new open question the popover creates — its
positioning/anchor per host — is surfaced at §8e. Author: product-designer. Date: 2026-06-17. **No code has
been written by this doc.**
This phase has **two deliverables that share one engine**: This phase has **two deliverables that share one engine**:
1. **Generalize** the landed Mix waveform visualizer (the WebGL2 lava renderer + its seven-knob controls) 1. **Generalize** the landed Mix waveform visualizer (the WebGL2 lava renderer + its eight-knob controls)
from a Mix-only treatment into a **track-cardinal visualizer** that every Release Detail page can host from a Mix-only treatment into a **track-cardinal visualizer** that every Release Detail page can host
— Cuts, Sessions, and Mixes alike — rendering the waveform of **whatever track is currently — Cuts, Sessions, and Mixes alike — rendering the waveform of **whatever track is currently
playing/selected** on that page. playing/selected** on that page.
@@ -38,7 +50,7 @@ endpoint shape, and the acceptance criteria below.
Cross-references (read before implementing): Cross-references (read before implementing):
- `product-notes/phase-10-mix-visualizer-lava-reframe.md` — the lava renderer this generalizes. The - `product-notes/phase-10-mix-visualizer-lava-reframe.md` — the lava renderer this generalizes. The
CPU-physics wax-blob model, the OKLab three-color gradient, the seven-knob control model, the bridge CPU-physics wax-blob model, the OKLab three-color gradient, the eight-knob control model, the bridge
contract, and the read-only contract all **carry forward unchanged**. This spec does not re-derive any contract, and the read-only contract all **carry forward unchanged**. This spec does not re-derive any
of them; it changes *where the engine lives*, *what feeds it*, and *who hosts it*. of them; it changes *where the engine lives*, *what feeds it*, and *who hosts it*.
- `product-notes/mix-visualizer-webgl-renderer.md` — the renderer architecture (pipeline, datum-as-texture, - `product-notes/mix-visualizer-webgl-renderer.md` — the renderer architecture (pipeline, datum-as-texture,
@@ -47,8 +59,14 @@ Cross-references (read before implementing):
keyed on `ReleaseEntryKey` + `TrackId`, not on Mix.** Renamed and re-pointed by this phase (§3, §4). keyed on `ReleaseEntryKey` + `TrackId`, not on Mix.** Renamed and re-pointed by this phase (§3, §4).
- `DeepDrftPublic.Client/Controls/MixVisualizerControls.razor[.cs/.css]`, - `DeepDrftPublic.Client/Controls/MixVisualizerControls.razor[.cs/.css]`,
`DeepDrftPublic.Client/Services/MixVisualizerControlState.cs`, `DeepDrftPublic.Client/Services/MixVisualizerControlState.cs`,
`DeepDrftPublic.Client/Controls/MixZoomMapping.cs` — the controls + state + mapping. Renamed, otherwise `DeepDrftPublic.Client/Controls/MixZoomMapping.cs` — the controls + state + mapping. Renamed; the controls
unchanged. component becomes the **panel content** hosted inside a new popover wrapper (§3d-revised, §3g) rather than
an inline `TopRowCenter` bar. State + mapping otherwise unchanged.
- **New (this revision):** a popover host component — working name `WaveformVisualizerControlPopover` — that
pairs the lava-lamp trigger icon with the `WaveformVisualizerControls` panel as its overlay content. This
is the single affordance every host places (§3d-revised). MudBlazor `MudPopover` (already in the dependency
set) is the natural substrate. Styled to the NowPlaying Hero look (§3g) — no inline hex; tokens from
`DeepDrftShared.Client/wwwroot/styles/deepdrft-tokens.css`.
- `DeepDrftPublic/Interop/visualizer/MixVisualizer.ts` — the WebGL2 renderer module. Renamed; the only - `DeepDrftPublic/Interop/visualizer/MixVisualizer.ts` — the WebGL2 renderer module. Renamed; the only
*logic* change is how the datum's time-mapping is established when no high-res mix datum exists (§5). *logic* change is how the datum's time-mapping is established when no high-res mix datum exists (§5).
- `DeepDrftPublic.Client/Controls/ReleaseDetailScaffold.razor` — the shared detail chrome. The visualizer - `DeepDrftPublic.Client/Controls/ReleaseDetailScaffold.razor` — the shared detail chrome. The visualizer
@@ -114,9 +132,10 @@ its controls, mounted in **three hosting modes** (§3f) — *visualizer-is-the-p
environment* on Cut/Session detail, *contained live element* on the home-page NowPlaying card — each fed by environment* on Cut/Session detail, *contained live element* on the home-page NowPlaying card — each fed by
a **per-track high-res datum** that exists for every track (Direction B, §5). The visualizer always renders a **per-track high-res datum** that exists for every track (Direction B, §5). The visualizer always renders
**the currently playing/selected track's** datum; the release is the host, not the datum's owner. The lava **the currently playing/selected track's** datum; the release is the host, not the datum's owner. The lava
controls ride **every Release Detail host** — Mix, Cut, and Session all get the seven-knob bar + lava-lamp controls ride **every host** — Mix, Cut, Session, *and* the NowPlaying card — via a **single
toggle (full parity, §3d). The NowPlaying card drives the *same* engine off live playback (§6) and stays popover-hosted control panel** triggered by the lava-lamp icon (§3d-revised). Every host places one icon and
**controls-suppressed by default** (the one open sub-question, §8b-followup). gets the identical panel; full parity (including the NowPlaying card) is achieved through the popover, so
§8b-followup is closed. The NowPlaying card drives the *same* engine off live playback (§6).
**In scope.** **In scope.**
@@ -137,9 +156,13 @@ toggle (full parity, §3d). The NowPlaying card drives the *same* engine off liv
re-implementing the wrapper. re-implementing the wrapper.
- **Rewire the NowPlayingHero** to mount the visualizer driven by the live cascaded player, replacing the - **Rewire the NowPlayingHero** to mount the visualizer driven by the live cascaded player, replacing the
20 hardcoded CSS bars (§6). 20 hardcoded CSS bars (§6).
- **Mount the lava controls on every detail-page host** — Mix, Cut, and Session all carry the seven-knob - **Build the popover-hosted control panel** (§3d-revised, §3g). The renamed `WaveformVisualizerControls`
bar + lava-lamp toggle (full parity, §3d). The NowPlaying card mounts **controls-suppressed** by default becomes the **panel content**; a new `WaveformVisualizerControlPopover` host pairs the lava-lamp trigger
(§3d, §8b-followup). icon with that panel as overlay content. One panel, one popover host, placed by an icon anywhere — the
SOLID seam (§3d-revised). Style the panel to the **NowPlaying Hero look** (§3g) from real theme tokens.
- **Place the lava-lamp icon → popover on every host** — Mix, Cut, Session, *and* the NowPlaying card. Every
host's controls affordance is now identical: one icon, one popover, one panel. Full parity on all four
surfaces (§3d-revised) — §8b-followup is dissolved by the popover.
**Out of scope / unchanged.** **Out of scope / unchanged.**
@@ -148,7 +171,9 @@ toggle (full parity, §3d). The NowPlaying card drives the *same* engine off liv
changes its *input plumbing* and its *compute breadth*, never its art. changes its *input plumbing* and its *compute breadth*, never its art.
- **No bridge redesign.** The single-owner bridge, the idempotent datum guard, the `IsActivePlayer` - **No bridge redesign.** The single-owner bridge, the idempotent datum guard, the `IsActivePlayer`
gating, the `isPlaying`-gated rAF loop — all preserved. Extend the fetch, not the contract. gating, the `isPlaying`-gated rAF loop — all preserved. Extend the fetch, not the contract.
- **No new control model.** The seven knobs and `…ControlState` stay as-is (renamed). No new dials. - **No new control model.** The eight knobs and `…ControlState` stay as-is (renamed). No new dials. The
popover changes *where the controls are hosted* (a popover panel instead of an inline bar), not *what they
are* — same knobs, same state, same `Changed` seam.
- **No new high-res *algorithm*.** Direction B generalizes the *existing* duration-derived compute to run - **No new high-res *algorithm*.** Direction B generalizes the *existing* duration-derived compute to run
per-track for all media; it does not redesign how the high-res datum is computed (`MixWaveformResolution` per-track for all media; it does not redesign how the high-res datum is computed (`MixWaveformResolution`
carries forward — the same ~333 samples/sec duration-derived model). carries forward — the same ~333 samples/sec duration-derived model).
@@ -171,7 +196,8 @@ every later wave references the generalized names:
| `MixVisualizerControlState.cs` (+ DI registration) | `WaveformVisualizerControlState.cs` | | `MixVisualizerControlState.cs` (+ DI registration) | `WaveformVisualizerControlState.cs` |
| `MixZoomMapping.cs` | `WaveformZoomMapping.cs` | | `MixZoomMapping.cs` | `WaveformZoomMapping.cs` |
| `MixVisualizer.ts` (+ the `./js/visualizer/MixVisualizer.js` import path) | `WaveformVisualizer.ts` | | `MixVisualizer.ts` (+ the `./js/visualizer/MixVisualizer.js` import path) | `WaveformVisualizer.ts` |
| `DDIcons.LavaLamp` / `LavaLampFilled` | keep (the lava-lamp glyph is the *controls* affordance, now on every detail-page host — §3d) | | `DDIcons.LavaLamp` / `LavaLampFilled` | keep (the lava-lamp glyph is now the **popover trigger** — the single controls affordance, placed on every host — §3d-revised) |
| *(new)* | `WaveformVisualizerControlPopover.razor[.cs/.css]` — popover host pairing the lava-lamp trigger icon with the `WaveformVisualizerControls` panel (§3d-revised, §3g). Not a rename; new in 12.E. |
The `ReleaseEntryKey` / `TrackId` parameters and the fetch keep working unchanged through the rename. The `ReleaseEntryKey` / `TrackId` parameters and the fetch keep working unchanged through the rename.
The `mix-waveforms` vault name and `MixMetadata.WaveformEntryKey` stay (they are still where the high-res The `mix-waveforms` vault name and `MixMetadata.WaveformEntryKey` stay (they are still where the high-res
@@ -190,10 +216,11 @@ Generalizing does **not** mean flattening every medium to the same look. The cle
- **Shared (the engine):** the renderer, the bridge, the controls component, the state, the datum - **Shared (the engine):** the renderer, the bridge, the controls component, the state, the datum
contract, the playback coupling, the read-only contract. One copy, consumed by all hosts. contract, the playback coupling, the read-only contract. One copy, consumed by all hosts.
- **Per-host (the composition):** *whether* the visualizer is mounted, *whether* the lava controls are - **Per-host (the composition):** *whether* the visualizer is mounted, *where the lava-lamp icon sits* (the
exposed, and *what datum* the host points it at. These ride host composition (slots + parameters), controls affordance is now a single popover-triggering icon — §3d-revised), and *what datum* the host
never a `switch (medium)` inside the engine. A medium that wants no visualizer mounts none; a medium points it at. These ride host composition (slots + parameters), never a `switch (medium)` inside the
that wants the ambient backdrop but no knobs mounts the backdrop with controls suppressed. engine. A medium that wants no visualizer mounts none; a host that wants the controls places the lava-lamp
icon and the identical popover panel follows.
This is the same "variance rides a slot, never a flag" discipline the scaffold already uses for This is the same "variance rides a slot, never a flag" discipline the scaffold already uses for
`Header`/`Hero`/`TopRightAction` (Phase 9 §5.3) — extended to the ambient mount. `Header`/`Hero`/`TopRightAction` (Phase 9 §5.3) — extended to the ambient mount.
@@ -217,17 +244,21 @@ into the scaffold so it is the default, not a Mix bespoke). A host that supplies
plain background (Liskov: absent slot = no ambient layer, no regression). plain background (Liskov: absent slot = no ambient layer, no regression).
- **Cut** (composes the scaffold) supplies `<WaveformVisualizer ReleaseEntryKey=… TrackId=… />` to - **Cut** (composes the scaffold) supplies `<WaveformVisualizer ReleaseEntryKey=… TrackId=… />` to
`Ambient` **with the full seven-knob bar + lava-lamp toggle exposed** (full parity, §3d) — a living `Ambient` and places the **lava-lamp icon → popover panel** (full parity, §3d-revised) — a living
waveform field behind the album hero + track list, rendering the *currently selected/playing track's* waveform field behind the album hero + track list, rendering the *currently selected/playing track's*
datum, tunable in place. **§8b is resolved: Cut and Session get the ambient layer AND the controls datum, tunable in place via the popover. **§8b is resolved: Cut and Session get the ambient layer AND the
(full parity, not Mix-only).** The ambient *layout* (visualizer behind hero+content) is unchanged; the controls (full parity, not Mix-only).** The ambient *layout* (visualizer behind hero+content) is
difference from the earlier draft is that the controls are no longer suppressed here. unchanged; the difference from the earlier draft is that the controls are present, and (this revision)
they live in a popover behind the lava-lamp icon rather than an inline bar.
- **Session** does not compose the scaffold (§3e) — it mounts the ambient visualizer directly behind its - **Session** does not compose the scaffold (§3e) — it mounts the ambient visualizer directly behind its
own hero overlay, the same engine, its own thin full-bleed wrapper. own hero overlay, the same engine, its own thin full-bleed wrapper.
- **Mix does *not* use this slot.** Mix is the *visualizer-is-the-page* mode (§3f) and keeps its existing - **Mix does *not* use this slot.** Mix is the *visualizer-is-the-page* mode (§3f) and keeps its existing
full-bleed mount + `TopRowCenter` knob-bar + lava-lamp toggle exactly as the Phase 10 reframe landed full-bleed mount as the Phase 10 reframe landed it. **The one controls change for Mix in this revision:**
them. The `Ambient` slot is for the *ambient* mode only — folding Mix into it would force the slot to its `TopRowCenter` inline knob-bar is replaced by the lava-lamp icon → popover panel (§3d-revised), the
carry both "the page" and "behind the page," which is the conflation §3f exists to avoid. same affordance every other host now uses. The `TopRightAction` lava-lamp glyph Mix already mounts becomes
the popover trigger directly — a small, contained change to Mix, not a redesign. The `Ambient` slot is for
the *ambient* mode only — folding Mix into it would force the slot to carry both "the page" and "behind
the page," which is the conflation §3f exists to avoid.
**Why the scaffold, not each page.** The full-bleed wrapper, the foreground stacking context, and the **Why the scaffold, not each page.** The full-bleed wrapper, the foreground stacking context, and the
footer-clip plumbing (the dynamic-footer overflow clip from the reframe §2c) are all *chrome*, and the footer-clip plumbing (the dynamic-footer overflow clip from the reframe §2c) are all *chrome*, and the
@@ -235,36 +266,51 @@ scaffold is where chrome lives. Putting the ambient layer on the scaffold means
stacking context, and the mount point are written once. `SessionDetail` is the lone holdout that doesn't stacking context, and the mount point are written once. `SessionDetail` is the lone holdout that doesn't
compose the scaffold today — see §3e. compose the scaffold today — see §3e.
### 3d. Where do the lava controls live per host? (the controls boundary — full parity, resolved) ### 3d. Where do the lava controls live? (the controls boundary — popover-hosted, full parity, resolved)
The seven-knob lava bar is an **expert tuning surface** whose identity is "the lava lamp." **Daniel's §8b **Revised model (Daniel, 2026-06-17): a single popover-hosted control panel, triggered by the lava-lamp
call: full parity on the detail pages.** The controls ride **every Release Detail host**: icon.** *"We have enough [controls] now that I want to design a panel to be hosted in a popover for the
visualizer controls. The lava-lamp toggle should be wired to this popover, so anywhere we can put one Icon
we can put the control surface."* This supersedes the inline knob-*bar* model (where each detail page laid
out its own eight-knob bar). The controls are now **one panel behind one icon**, placed identically on
every host.
- **Mix, Cut, and Session detail pages all carry the seven-knob bar + lava-lamp toggle.** The The mechanics:
`WaveformVisualizerControls` mount is **no longer Mix-suppressed** — it rides every detail-page mount.
On Mix (mode A) the bar sits in `TopRowCenter` over the full-bleed visualizer as the Phase 10 reframe
landed it; on Cut and Session (mode B) the same controls bar + lava-lamp toggle ride the ambient mount,
letting a visitor tune the living field behind the release. The *layout* still differs per mode (§3f) —
visualizer-is-the-page vs. ambient-behind-content — but the **controls presence is uniform across all
three detail hosts.** Rationale (Daniel): the lava is a signature affordance of the site; giving every
release page the tuning surface makes the whole site feel alive and consistent, not just the Mix page.
The shared `WaveformVisualizerControlState` supplies the dial values and is where each mount reads/writes,
so tuning behaves identically wherever the bar appears (see the state-scoping note below).
- **The NowPlaying home card (mode C) stays controls-suppressed by default.** Full parity was resolved
against the Release *Detail* pages; the NowPlaying card is the *contained* mode-C host — a small hero
panel, not a full page — where a seven-knob tuning bar may be awkward. Default: the card mounts the
ambient/contained field with **no knobs, no lava-lamp button**, rendering at the shared tuned defaults.
**This is the one open sub-question — see §8b-followup.** The seam supports controls-on-the-card either
way (the controls are a separate component over shared state), so flipping it later is a one-line
composition change, not an engine change.
> **State-scoping note (raised by full parity, staff-engineer's call at 12.C).** With the controls now - **The lava-lamp icon is the single affordance.** Click the icon → the control panel pops over. There is
> present on Mix *and* Cut *and* Session, decide whether `WaveformVisualizerControlState` is **one shared no separate inline bar and no separate toggle; the icon *is* the toggle and the popover *is* the panel.
> tuning** (a knob turned on a Cut page is the same lava everywhere — single source of truth, simplest, and Closed state is just the icon (compact, unobtrusive). Open state floats the panel over the surface.
> consistent with the "one engine" framing) or **per-host/per-mode scoped** (each surface remembers its own - **One panel, one popover host, placed by an icon anywhere.** The renamed `WaveformVisualizerControls`
> dial positions). **Recommend one shared state** — it matches the single-engine model, it is what the becomes the **panel content** (the eight RadialKnobs, unchanged). A new `WaveformVisualizerControlPopover`
> persisted `WaveformVisualizerControlState` already is, and "the lava lamp has one set of dials" is the host pairs the lava-lamp trigger icon with that panel as its overlay content (MudBlazor `MudPopover` the
> simpler mental model. Flag only; not a product blocker. natural substrate). Every host renders exactly this one component; the panel is byte-for-byte identical
everywhere. **This is the SOLID seam, named precisely:** *one panel component, one popover host, placed by
an icon anywhere.*
- **Full parity across all four surfaces, the popover way.** Mix, Cut, Session, **and** the NowPlaying card
each place the lava-lamp icon and get the identical popover panel. There is no per-host control divergence
left to decide — the variance that *used* to live in "which page shows the bar" is dissolved because every
host shows the same icon. The only per-host question that remains is *where the icon sits and where the
popover anchors* (§8e), which is positioning, not presence.
- **Why this is more DRY than the inline bar.** The earlier model asked each detail page to host the
controls bar in its own layout (Mix in `TopRowCenter`, Cut/Session in the ambient composition, the
NowPlaying card suppressed because a bar didn't fit). That is three-to-four *different* control
compositions over one shared state. The popover collapses them to **one** composition — `<icon → popover →
panel>` — reused verbatim. The awkwardness that justified suppressing controls on the small NowPlaying card
evaporates: a popover doesn't compete with the card's compact layout the way an inline bar would, because
it's closed by default and floats when opened. **That is what dissolves §8b-followup** (below).
**§8b-followup is dissolved, not deferred.** The old open sub-question — "do the full-parity controls extend
onto the small NowPlaying card?" — existed *because* an inline eight-knob bar was awkward on a compact card.
With the popover, the card places the same lava-lamp icon as every other host and the panel floats on
demand; there is no compact-layout conflict to weigh. **Resolution: the NowPlaying card gets the icon →
popover like everywhere else — full parity on all four surfaces.** (See §8b-followup, now marked resolved.)
> **State-scoping note (staff-engineer's call at 12.E).** With one shared panel surfaced on every host,
> `WaveformVisualizerControlState` should be **one shared tuning** — a knob turned in the popover on a Cut
> page is the same lava everywhere. This is even more natural under the popover than under the inline-bar
> model: there is literally one panel, so "one set of dials" is the only coherent reading. It matches the
> single-engine framing and is what the persisted `WaveformVisualizerControlState` already is. Flag only;
> not a product blocker.
### 3e. The `SessionDetail` scaffold question ### 3e. The `SessionDetail` scaffold question
@@ -277,9 +323,11 @@ Session gets the ambient layer at all — don't reopen the Session-vs-scaffold d
compose the scaffold, so Cut gets the ambient layer for free via the slot. This asymmetry is fine: the compose the scaffold, so Cut gets the ambient layer for free via the slot. This asymmetry is fine: the
slot serves scaffold-composing pages; the one non-composing page mounts the shared engine directly. The slot serves scaffold-composing pages; the one non-composing page mounts the shared engine directly. The
*engine* is still single-source either way — only the *mount* differs, which is exactly the per-host *engine* is still single-source either way — only the *mount* differs, which is exactly the per-host
variance §3b sanctions. **Under full parity (§3d), Session's direct mount also carries the seven-knob bar + variance §3b sanctions. **Under full parity (§3d-revised), Session also places the lava-lamp icon → popover
lava-lamp toggle** — the controls bar rides Session's own top-row composition the same way Cut's rides the panel** — the same single affordance every host uses. Because the controls are now one popover behind one
scaffold's, so Session is full-parity even though it doesn't compose the scaffold. icon (not an inline bar threaded into a page's top row), Session's lack of scaffold composition is a
non-issue for controls: it just places the icon wherever its own chrome puts it. Session is full-parity even
though it doesn't compose the scaffold.
### 3f. Three hosting modes of the one engine (the elaboration §8b asked for) ### 3f. Three hosting modes of the one engine (the elaboration §8b asked for)
@@ -289,37 +337,75 @@ out surface-by-surface, because the slot design and the naming follow from this
| Mode | Surfaces | What the visualizer *is* on screen | Controls | Mount mechanism | | Mode | Surfaces | What the visualizer *is* on screen | Controls | Mount mechanism |
|------|----------|-----------------------------------|----------|-----------------| |------|----------|-----------------------------------|----------|-----------------|
| **A — Visualizer-is-the-page** | **Mix detail** | The full-bleed centerpiece. The lava field **is** the page; the mix details sit *over* it as a thin overlay. You came here to watch the lava. | Seven-knob bar + lava-lamp toggle (`TopRowCenter`) | Mix's own full-bleed mount (unchanged from Phase 10 reframe) — **not** the `Ambient` slot | | **A — Visualizer-is-the-page** | **Mix detail** | The full-bleed centerpiece. The lava field **is** the page; the mix details sit *over* it as a thin overlay. You came here to watch the lava. | Lava-lamp icon → popover panel (§3d-revised) | Mix's own full-bleed mount (unchanged from Phase 10 reframe) — **not** the `Ambient` slot |
| **B — Ambient environment** | **Cut detail, Session detail** | Living texture *behind and around* the hero + content. The album cover / track list / session hero is the subject; the waveform is environment that makes the page feel alive. You came here for the release; the lava is atmosphere. | **Seven-knob bar + lava-lamp toggle (full parity, §3d)** | `ReleaseDetailScaffold.Ambient` slot (Cut) / direct full-bleed mount (Session, §3e) | | **B — Ambient environment** | **Cut detail, Session detail** | Living texture *behind and around* the hero + content. The album cover / track list / session hero is the subject; the waveform is environment that makes the page feel alive. You came here for the release; the lava is atmosphere. | Lava-lamp icon → popover panel (full parity, §3d-revised) | `ReleaseDetailScaffold.Ambient` slot (Cut) / direct full-bleed mount (Session, §3e) |
| **C — Contained live element** | **NowPlaying card** (home) | A small, bounded live panel *inside* the card. Not full-bleed, not "behind" anything — a contained element that shows the real signal of whatever is playing. | Suppressed by default (§8b-followup) | Contained mount, `Fill`-mode canvas sized to the card, not the viewport (§6c) | | **C — Contained live element** | **NowPlaying card** (home) | A small, bounded live panel *inside* the card. Not full-bleed, not "behind" anything — a contained element that shows the real signal of whatever is playing. | Lava-lamp icon → popover panel (full parity — the popover dissolves the old suppression, §3d-revised) | Contained mount, `Fill`-mode canvas sized to the card, not the viewport (§6c) |
**Why three modes and not "backdrop everywhere":** the three differ in *what the user's eye treats as the **Why three modes and not "backdrop everywhere":** the three differ in *what the user's eye treats as the
subject*. On Mix the visualizer **is** the subject (mode A). On Cut/Session the *release* is the subject subject*. On Mix the visualizer **is** the subject (mode A). On Cut/Session the *release* is the subject
and the visualizer is environment (mode B — this is the only mode that is genuinely a "backdrop"). On the and the visualizer is environment (mode B — this is the only mode that is genuinely a "backdrop"). On the
home card the visualizer is a *bounded live readout* of current playback (mode C). Same engine, same datum home card the visualizer is a *bounded live readout* of current playback (mode C). Same engine, same datum
contract, same renderer — three compositions. This is the SOLID payoff stated precisely: the variance is contract, same renderer — three compositions. This is the SOLID payoff stated precisely: the variance is
entirely in **hosting composition** (full-bleed vs. slot vs. contained; viewport-sized vs. container-sized; entirely in **hosting composition** (full-bleed vs. slot vs. contained; viewport-sized vs. container-sized),
controls present on all three detail hosts, suppressed-by-default only on the contained home card), never in never in the engine. **The controls are no longer a per-mode discriminator at all** — under the
the engine. **Note the controls are no longer a per-mode discriminator on the detail pages** — modes A and B popover-hosted model (§3d-revised) all four surfaces place the *same* lava-lamp icon → popover panel. The
both carry the full seven-knob bar (§3d, §8b resolved to full parity); only the mode-C home card suppresses only per-mode controls nuance left is *where the icon anchors* (§8e), which is positioning, not presence.
them by default (§8b-followup).
**What this resolves about the slot.** Because Mix (mode A) keeps its own mount and the NowPlaying card **What this resolves about the slot.** Because Mix (mode A) keeps its own mount and the NowPlaying card
(mode C) is a contained mount, the `ReleaseDetailScaffold` slot serves **mode B only** — and that is why it (mode C) is a contained mount, the `ReleaseDetailScaffold` slot serves **mode B only** — and that is why it
is named `Ambient`, not `Backdrop` and not `Visualizer`. A `Visualizer` slot would imply Mix routes through is named `Ambient`, not `Backdrop` and not `Visualizer`. A `Visualizer` slot would imply Mix routes through
it too; an `Ambient` slot says exactly what it carries: the behind-the-content environment layer for it too; an `Ambient` slot says exactly what it carries: the behind-the-content environment layer for
scaffold-composing detail pages. Mode A and mode C mount the engine without the slot. scaffold-composing detail pages. Mode A and mode C mount the engine without the slot. The **controls popover
is orthogonal to the slot** — it's an icon any host places, independent of how the *visualizer layer* is
mounted (slot vs. full-bleed vs. contained).
**§8b status: RESOLVED — modes A + B + C all live, with FULL PARITY on the controls across all three detail **§8b status: RESOLVED — modes A + B + C all live, with FULL PARITY on the controls across all four hosts
hosts (Daniel, 2026-06-17).** All three hosting modes ship (Mix is the page, Cut/Session are ambient, the via the popover (Daniel, 2026-06-17).** All three hosting *modes* ship (Mix is the page, Cut/Session are
home card is contained) **and** the seven-knob lava bar + lava-lamp toggle ride every Release Detail host — ambient, the home card is contained) **and** the lava-lamp controls ride every host — Mix, Cut, Session, and
Mix, Cut, and Session — not Mix-only. The earlier "mode 1 (Mix + NowPlaying only, Cut/Session plain)" the NowPlaying card — via the single popover-hosted panel (§3d-revised). The earlier "mode 1 (Mix +
fallback is closed; the earlier "controls Mix-only" default is overridden by Daniel's full-parity call. The NowPlaying only, Cut/Session plain)" fallback is closed; the earlier "controls Mix-only" default is
Cut/Session ambient treatment **plus its in-place tuning controls** is the distinctive-feel win the overridden; and the "is the NowPlaying card controls-suppressed?" sub-question is **dissolved by the
generalization buys; the `Ambient` slot carries the field and the controls bar rides the host composition popover** (§8b-followup, now resolved). Each host is a one-visualizer-mount-plus-one-lava-lamp-icon
alongside it, so each detail page is a one-mount-plus-controls-bar composition. **One sub-question remains composition. The remaining open item is the popover's anchor/positioning per host (§8e) — a layout detail,
(§8b-followup):** whether full parity also extends the seven-knob bar onto the small NowPlaying home card not a presence decision.
(mode C) — default **no** (the card stays controls-suppressed), flagged for Daniel.
### 3g. Panel styling — the NowPlaying Hero look (theme tokens, no hardcoded hex)
Daniel's styling direction for the popover panel: **"same look and feel as the NowPlaying Hero."** Concretely:
- **Dark-navy primary background** — the same navy ground the NowPlaying Hero card sits on.
- **Green-accent knobs** with **light-colored icons**.
- **Muted-navy filler / circular border fill** around the knobs.
These map directly onto the existing token layer
(`DeepDrftShared.Client/wwwroot/styles/deepdrft-tokens.css` — the single source of truth; the Phase 10
reframe's "one source, no hardcoded hexes" discipline holds). The mapping staff-engineer should pull from:
| Panel element | Daniel's intent | Token (source of truth) | Value (for reference only — *use the token, not the hex*) |
|---------------|-----------------|--------------------------|-----------------------------------------------------------|
| Panel background | dark-navy primary | `--deepdrft-navy` (ground) / `--deepdrft-navy-mid` (elevated panel) | `#112338` / `#17283f` |
| Knob accent / active arc | green accent | `--deepdrft-green-accent` | `#3D7A68` |
| Knob icons / labels | light-colored | `--deepdrft-white` | `#FAFAF8` |
| Knob filler / circular border fill | muted-navy | `--deepdrft-navy-mid` (fill) / `--deepdrft-muted` (border-muted) | `#17283f` / `#8A9BB0` |
| Panel border / divider | subtle | `--deepdrft-border` (or the dark-theme `rgba(250,250,248,0.10)` divider) | — |
The NowPlaying Hero card itself (`NowPlayingCard.razor.css`) renders on a translucent off-white wash
(`rgba(250,250,248,0.06)` over the page's navy ground, `backdrop-filter: blur(8px)`) with the green accent
on its label/dot (`--deepdrft-green-accent`) and off-white title (`--deepdrft-white`). The panel should read
as a sibling of that card — the same navy-ground/green-accent/off-white vocabulary, the same restrained
translucency-over-navy feel — so a popover opened anywhere looks like it belongs to the same family as the
home card. **Staff-engineer pulls the tokens above; no inline hexes.** If a token is missing for a needed
shade (e.g. a knob's inactive track fill), prefer a `color-mix()` over the nearest token to a new hardcoded
value, and flag the gap rather than minting an untokened hex.
> **Note on the MudBlazor palette.** The MudBlazor theme (`DeepDrftShared.Client/Common/DeepDrftPalettes.cs`,
> applied via `<MudThemeProvider Theme="DeepDrftPalettes.Default">`) carries the *same* navy/green/off-white
> vocabulary in its `PaletteDark` (`Primary = #3D7A68` green-accent, `Background = #0D1B2A` navy,
> `Surface = #162437` navy-mid, `TextPrimary = #FAFAF8` off-white). For MudBlazor-component-level theming
> inside the popover, prefer `Color="Color.Primary"`/`Color.Surface` against that palette over CSS overrides
> where the component supports it; reach for the CSS `--deepdrft-*` tokens for the bespoke knob/panel chrome
> the RadialKnob owns. Either way the colors trace back to one source — the tokens file and the palette are
> the same vocabulary — so there is no third place to hardcode.
--- ---
@@ -479,15 +565,16 @@ datum (Direction B), the home card renders at full fidelity for *any* track, not
the per-track model already requires for a multi-track Cut (where the release is fixed but the track the per-track model already requires for a multi-track Cut (where the release is fixed but the track
scrolls). The two converge on one behavior: **re-fetch when the current track's identity changes.** scrolls). The two converge on one behavior: **re-fetch when the current track's identity changes.**
Verify the guard keys on the current track, not the release. Verify the guard keys on the current track, not the release.
- **Small surface, controls-suppressed by default.** The card is a small hero panel, not a full-bleed page. - **Small surface, popover controls (full parity via the icon).** The card is a small hero panel, not a
Mount the visualizer **controls-suppressed** by default (no lava-lamp, no knob bar) and sized to the card, full-bleed page — but under the popover-hosted model (§3d-revised) that is no longer a reason to suppress
not `position: fixed`. **Note this is now the *exception*, not the rule:** under Daniel's §8b full-parity controls. The card places the **same lava-lamp icon → popover panel** as every other host; the panel
call the controls ride every Release *Detail* page (Mix, Cut, Session — §3d); the NowPlaying card is the floats over the page on demand and doesn't compete with the card's compact title/sub layout the way an
one host that suppresses them by default, because a seven-knob tuning bar on a small home-card may be inline eight-knob bar would have. **This is what dissolved §8b-followup** — the popover removed the
awkward. **Whether full parity extends here too is the open sub-question, §8b-followup** — the seam awkwardness that justified suppression. The card's lava-lamp icon sits in/near the card chrome (its anchor
supports it either way (the controls are a separate component over shared state). **Flagged for is one of the §8e positioning calls — a small card is the tightest anchor case). The visualizer canvas is
staff-engineer (§8d):** the still sized to the card, not `position: fixed` (§6c container-sizing below). **Flagged for staff-engineer
renderer's footer-clip + full-viewport assumptions (`position: fixed; inset: 0`, clip-to-footer) are (§8d):** the renderer's footer-clip + full-viewport assumptions (`position: fixed; inset: 0`,
clip-to-footer) are
written for a full-page backdrop; mounting it in a *contained card* needs the canvas to size to its written for a full-page backdrop; mounting it in a *contained card* needs the canvas to size to its
container instead of the viewport. This is a real renderer-hosting wrinkle — the engine assumes container instead of the viewport. This is a real renderer-hosting wrinkle — the engine assumes
full-window today. Either (i) parameterize the visualizer's sizing (full-viewport vs. full-window today. Either (i) parameterize the visualizer's sizing (full-viewport vs.
@@ -498,9 +585,10 @@ datum (Direction B), the home card renders at full fidelity for *any* track, not
- **Performance.** A WebGL2 lava render on the *home page* (the highest-traffic, first-paint surface) is a - **Performance.** A WebGL2 lava render on the *home page* (the highest-traffic, first-paint surface) is a
heavier ask than on a detail page the user navigated to deliberately. Keep the existing `MAX_DPR = 2` heavier ask than on a detail page the user navigated to deliberately. Keep the existing `MAX_DPR = 2`
cap and the `isPlaying`-gated rAF loop (it burns no frames when nothing plays — so an idle home page cap and the `isPlaying`-gated rAF loop (it burns no frames when nothing plays — so an idle home page
pays nothing). If the lava is too heavy for the home card specifically, the controls-suppressed ambient pays nothing). If the lava is too heavy for the home card specifically, the card's mount
backdrop can run a *cheaper* preset (fewer blobs) via a density default — but do not fork the renderer; can run a *cheaper* preset (fewer blobs) via a density default — but do not fork the renderer;
use the existing density dial. **Flagged, not committed (§8d).** use the existing density dial (now adjustable from the card's own popover too). **Flagged, not committed
(§8d).**
### 6d. What the card keeps ### 6d. What the card keeps
@@ -519,6 +607,15 @@ content + upload + CMS + backfill + fetch slice; the spec splits it into 12.B1 (
backfill) and 12.B2 (the bridge's per-track fetch) so the heavy data work and the thin client rewire can be backfill) and 12.B2 (the bridge's per-track fetch) so the heavy data work and the thin client rewire can be
sequenced and tested apart. sequenced and tested apart.
**The controls work now consolidates into its own wave, 12.E** (the popover-panel revision). Under the
inline-bar model the controls were threaded into each detail-page mount, so 12.C carried "ambient layer +
controls bar" together. The popover model **pulls the controls out of the per-host mounts into one shared
component** built once (12.E) and *placed* by each host (an icon, §3d-revised). This is cleaner: 12.C and
12.D each reduce to "mount the visualizer layer + place the lava-lamp icon," and the panel/popover/styling
all live in one wave. 12.E depends only on the rename (12.A) — it touches the controls component + a new
popover host + the panel styling, none of which need the per-track fetch — so it can run in parallel with
the data work (12.B1/12.B2).
- **12.A — Rename to the abstraction (mechanical, no behavior change).** `Mix*` → `Waveform*` across the - **12.A — Rename to the abstraction (mechanical, no behavior change).** `Mix*` → `Waveform*` across the
five files + the TS module + the import path + the DI registration (§3a). **Load-bearing prerequisite** five files + the TS module + the import path + the DI registration (§3a). **Load-bearing prerequisite**
— every later wave references the generalized names. Acceptance: Mix detail behaves identically; — every later wave references the generalized names. Acceptance: Mix detail behaves identically;
@@ -535,30 +632,45 @@ sequenced and tested apart.
`EntryKey` and re-fetches on track-change (§4). **Depends on 12.A (renamed bridge) + 12.B1 (a datum to `EntryKey` and re-fetches on track-change (§4). **Depends on 12.A (renamed bridge) + 12.B1 (a datum to
fetch).** Acceptance: the Mix detail page renders the same high-res lava via the new track-cardinal fetch; fetch).** Acceptance: the Mix detail page renders the same high-res lava via the new track-cardinal fetch;
a non-Mix track now returns a high-res datum. a non-Mix track now returns a high-res datum.
- **12.E — Popover-hosted control panel (the controls revision).** Turn the renamed
`WaveformVisualizerControls` into the **panel content** and build the `WaveformVisualizerControlPopover`
host pairing the lava-lamp trigger icon with that panel as overlay content (`MudPopover`, §3d-revised).
Style the panel to the **NowPlaying Hero look** from theme tokens (§3g — no hardcoded hex). Make the
state-scoping call (one shared `WaveformVisualizerControlState`, §3d-revised note). **Depends on 12.A**
(renamed controls component) only — no per-track datum needed, so it runs **parallel to 12.B**. Acceptance:
the lava-lamp icon opens a popover panel with all eight knobs, styled to the Hero look; turning a knob
drives the visualizer (where one is mounted) via the unchanged `Changed` seam; the panel is one component
reused everywhere. **This wave is the unit every host then places (§3d-revised).**
- **12.C — `Ambient` slot on the scaffold + mount on detail pages (mode B, §3f).** Promote the - **12.C — `Ambient` slot on the scaffold + mount on detail pages (mode B, §3f).** Promote the
full-bleed/foreground/footer-clip pattern into `ReleaseDetailScaffold` as an optional `Ambient` slot full-bleed/foreground/footer-clip pattern into `ReleaseDetailScaffold` as an optional `Ambient` slot
(§3c); Cut mounts the ambient layer **with the full seven-knob bar + lava-lamp toggle** (full parity, (§3c); Cut mounts the ambient layer **and places the lava-lamp icon → popover** (full parity, §3d-revised);
§3d); Session mounts directly **also full-parity** (§3e). Mix is **unchanged** (mode A keeps its own Session mounts directly **also full-parity** (§3e). Mix is **unchanged** as a visualizer layer (mode A
mount + controls). **Depends on 12.B2** (a datum to render). **§8b is resolved (full parity, no longer keeps its own full-bleed mount); its only controls change is swapping the inline `TopRowCenter` bar for the
gated)** — Cut and Session ship with both the ambient layer and the controls. This wave also makes the lava-lamp icon → popover (folded into 12.E's affordance). **Depends on 12.B2** (a datum to render) **and
state-scoping call (§3d note — recommend one shared `WaveformVisualizerControlState`). Acceptance: Mix 12.E** (the popover to place). **§8b is resolved (full parity, no longer gated)** — Cut and Session ship
unchanged; Cut and Session each show an ambient living layer **with a working seven-knob bar + lava-lamp with both the ambient layer and the popover controls. Acceptance: Mix unchanged as a layer; Cut and Session
toggle**, rendering the current track's datum, no regression to the hero/content. each show an ambient living layer **with a working lava-lamp icon → popover panel**, rendering the current
- **12.D — NowPlayingHero rewire (mode C, §3f).** Replace the synthetic bars with a contained, track's datum, no regression to the hero/content.
controls-suppressed `<WaveformVisualizer>` driven by the live player, pointed at the current track (§6); - **12.D — NowPlayingHero rewire (mode C, §3f).** Replace the synthetic bars with a contained
add the `Fill`/container-sizing mode (§6c). **Depends on 12.A + 12.B2** (renamed engine + a per-track `<WaveformVisualizer>` driven by the live player, pointed at the current track (§6); add the
datum for whatever's playing). **Independent of 12.C** (different host; doesn't need the scaffold slot). `Fill`/container-sizing mode (§6c); **place the lava-lamp icon → popover on the card** (full parity, §6c —
Acceptance: the home card shows the *real* high-res waveform of the playing track and sits at-rest when the popover dissolves the old suppression). **Depends on 12.A + 12.B2 + 12.E** (renamed engine + a
nothing plays; no synthetic bars remain. per-track datum + the popover to place). **Independent of 12.C** (different host; doesn't need the scaffold
slot). Acceptance: the home card shows the *real* high-res waveform of the playing track, sits at-rest when
nothing plays, and carries the lava-lamp icon → popover like every other host; no synthetic bars remain.
**Dependency shape:** `12.A → 12.B2 → (12.C ‖ 12.D)`, with **12.B1 a parallel server-side track** that **Dependency shape:** `12.A → 12.B2 → (12.C ‖ 12.D)`, with **12.B1 a parallel server-side track** that
12.B2 depends on (`12.B1 → 12.B2`) but that can start cold day one (it needs no renamed client identifiers). 12.B2 depends on (`12.B1 → 12.B2`) but that can start cold day one (it needs no renamed client identifiers),
12.A is the cheap mechanical unblock; **12.B1 is the new load-bearing heavy** (compute + backfill); 12.B2 is and **12.E (the popover controls) a third parallel track** depending only on 12.A — both 12.C and 12.D now
the thin client rewire that consumes it; 12.C (detail-page ambient hosts, **full-parity controls**) and 12.D also depend on 12.E to place the icon. So: `12.A → {12.B1 → 12.B2, 12.E}`, then `(12.B2 ∧ 12.E) → (12.C ‖
(NowPlaying host, controls-suppressed by default) are independent siblings off 12.B2. The cold-start items 12.D)`. 12.A is the cheap mechanical unblock; **12.B1 is the load-bearing heavy** (compute + backfill); 12.B2
are **12.A** (touches everything, risks nothing) and **12.B1** (the data work — start it early; the backfill is the thin client rewire that consumes it; **12.E is the controls consolidation** (one panel + popover +
gate is the only thing blocking it). With §8b resolved to full parity, 12.C no longer carries a mode-1 styling, built once); 12.C (detail-page ambient hosts) and 12.D (NowPlaying host) are independent siblings
fallback branch — Cut and Session are in, with controls. that each mount a visualizer layer and place the popover. The cold-start items are **12.A** (touches
everything, risks nothing), **12.B1** (the data work — start it early; the backfill gate is the only thing
blocking it), and **12.E** (the controls panel — independent of all the data work). With §8b resolved to
full parity *and* the popover dissolving §8b-followup, 12.C and 12.D carry no controls-suppression branch —
every host places the same icon.
--- ---
@@ -572,31 +684,46 @@ fallback branch — Cut and Session are in, with controls.
- **§8c (was: multi-track Cut's waveform) → dissolved by the per-track model.** The datum is per-track; the - **§8c (was: multi-track Cut's waveform) → dissolved by the per-track model.** The datum is per-track; the
visualizer renders the *current* track's datum, so there is no release-level "which track represents the visualizer renders the *current* track's datum, so there is no release-level "which track represents the
album" choice to make. The old "first track by `TrackNumber`" recommendation is moot. album" choice to make. The old "first track by `TrackNumber`" recommendation is moot.
- **§8b (Cut/Session hosting + controls) → RESOLVED: all three modes live, FULL PARITY on controls.** - **§8b (Cut/Session hosting + controls) → RESOLVED: all three modes live, FULL PARITY on controls, now
Daniel chose **option (3) — full parity on the lava controls** (over option (2) Mix-only controls): the POPOVER-HOSTED.** Daniel chose **option (3) — full parity on the lava controls** (over option (2) Mix-only
visualizer rides every Release Detail host (Mix, Cut, Session) **and** the seven-knob bar + lava-lamp controls): the visualizer rides every Release Detail host (Mix, Cut, Session) **and** the controls ride all
toggle ride all three, not just Mix. The three-hosting-mode *layout* framing (§3f — visualizer-is-the-page three. The three-hosting-mode *layout* framing (§3f — visualizer-is-the-page on Mix, ambient on
on Mix, ambient on Cut/Session, contained on NowPlaying) is retained; "backdrop" stays retired as the Cut/Session, contained on NowPlaying) is retained; "backdrop" stays retired as the wrong word for Mix.
wrong word for Mix. The only thing the flip changed: the `WaveformVisualizerControls` mount is **no longer **Controls-hosting revision (2026-06-17):** the controls are no longer an inline knob *bar* per page — they
Mix-suppressed** on Cut/Session. The earlier mode-1 fallback (Mix + NowPlaying only) and the "controls are a **single popover-hosted panel** triggered by the lava-lamp icon, placed identically on every host
Mix-only" default are both closed. One sub-question spun out — §8b-followup below. (§3d-revised). The earlier mode-1 fallback (Mix + NowPlaying only) and the "controls Mix-only" default are
both closed. The sub-question that spun out (§8b-followup) is now **resolved by the popover** (below).
**Newly open (created by the full-parity flip and by Direction B + the per-track model):** - **§8b-followup (do full-parity controls extend onto the NowPlaying card?) → RESOLVED by the popover.**
This sub-question existed *because* an inline eight-knob bar was awkward on a small, compact, high-traffic
card — so the earlier draft kept the card controls-suppressed by default and flagged the question for
Daniel. **The popover-hosting revision dissolves it:** the card places the same lava-lamp icon as every
other host, and the panel floats on demand rather than occupying the card's layout. There is no
compact-layout conflict left to weigh. **Resolution: the NowPlaying card gets the icon → popover like
everywhere else — full parity on all four surfaces** (Mix, Cut, Session, NowPlaying card). The only
remaining controls nuance is *where the icon anchors per host* — a positioning detail, now §8e.
**§8b-followup — Do the full-parity controls extend onto the NowPlaying home card (mode C)?** **Newly open (created by the popover revision and by Direction B + the per-track model):**
Daniel's §8b full-parity call (option 3) was answered against the Release *Detail*-page framing — Mix, Cut,
and Session all get the seven-knob bar + lava-lamp toggle. The **NowPlaying home card** is a different host: **§8e — Popover anchor/positioning per host (created by the popover revision).**
the contained mode-C panel, a small hero card on the highest-traffic surface, **not** a full detail page. The popover dissolves the *presence* question but creates a *placement* one: where does the lava-lamp
The spec applies full-parity controls to the three detail pages and keeps the NowPlaying card trigger icon sit, and where does the panel anchor, on each host? The hosts differ enough that one anchor
**controls-suppressed by default** (a living waveform readout with no knobs). Open: does Daniel want the rule may not fit all:
seven-knob bar on the home card too, or does it stop at the detail pages? **A seven-knob tuning bar on a - **Mix (mode A, full-bleed)** — the icon can take Mix's existing `TopRightAction` slot (it already mounts
small now-playing card may be awkward** — it competes with the card's compact title/sub layout and puts an the lava-lamp glyph there); the panel anchors off that corner over the full-bleed field. Cleanest case.
expert affordance on the landing surface — which is why the default is *suppressed*. But the seam supports - **Cut/Session (mode B, ambient)** — the icon rides the page chrome (Cut via the scaffold, Session via its
it either way (the controls are a separate component over shared `WaveformVisualizerControlState`), so own composition); the panel floats over hero+content. Needs to clear the hero overlay and not collide with
flipping the card to controls-on is a one-line composition change in 12.D, not an engine change. the share/play affordances already in the top row.
**Recommend keeping the card controls-suppressed** (full parity = detail pages; the home card stays a clean - **NowPlaying card (mode C, small contained)** — the tightest case. A small card has little room for an
ambient readout) — but this is a Daniel call, surfaced not buried. Affects only 12.D's mount; everything icon, and a popover panel sized to the Hero look may be larger than the card itself — so the panel likely
else in the phase is settled by the full-parity resolution. anchors to the icon but **overflows the card bounds** (which is fine — popovers float above), yet its open
position must not cover the title/sub it's meant to accompany. Worth a deliberate anchor choice (e.g.
open upward/leftward away from the card body).
**Recommend:** a single `WaveformVisualizerControlPopover` with a per-host *anchor origin* parameter
(MudPopover already exposes `AnchorOrigin`/`TransformOrigin`), defaulted sensibly and overridden per host —
not a forked popover. This keeps one component while letting each host place it well. **Staff-engineer-owned
layout call; flagged for Daniel only because the card case (mode C) may look cramped and is worth a glance
in review.** Does not block the phase; the default anchor is shippable and tunable.
**§8a-new — How does the high-res backfill run, and is it gated?** **§8a-new — How does the high-res backfill run, and is it gated?**
Direction B means every *existing* track needs its per-track high-res datum computed once. Two shapes: Direction B means every *existing* track needs its per-track high-res datum computed once. Two shapes:
@@ -648,24 +775,31 @@ by a lava lamp on the landing page.
3. **Track-cardinal fetch.** `GET api/track/{trackEntryKey}/waveform` (or the agreed track-cardinal route) 3. **Track-cardinal fetch.** `GET api/track/{trackEntryKey}/waveform` (or the agreed track-cardinal route)
returns the current track's high-res datum, 404 → graceful blank. The bridge resolves the *current returns the current track's high-res datum, 404 → graceful blank. The bridge resolves the *current
track* and re-fetches on track change. track* and re-fetches on track change.
4. **Mix unchanged (mode A).** The Mix detail page still renders the high-res lava with the seven-knob bar + 4. **Mix unchanged as a layer (mode A); controls via popover.** The Mix detail page still renders the
lava-lamp toggle, visualizer-is-the-page, at parity with the Phase 10 reframe — now via the high-res lava visualizer-is-the-page at parity with the Phase 10 reframe — now via the track-cardinal
track-cardinal fetch. fetch. Its controls are reached through the **lava-lamp icon → popover panel** (replacing the inline
5. **Ambient + controls on Cut/Session (mode B, full parity).** A Cut and a Session detail page show an `TopRowCenter` bar), the same affordance every other host uses.
ambient living waveform layer rendering the *currently selected/playing track's* datum, **with a working 5. **Ambient + popover controls on Cut/Session (mode B, full parity).** A Cut and a Session detail page show
seven-knob bar + lava-lamp toggle** (full parity — controls are present and tunable in place, not an ambient living waveform layer rendering the *currently selected/playing track's* datum, **with a
suppressed), with no regression to the hero/content. working lava-lamp icon → popover control panel** (full parity — controls present and tunable in place via
6. **NowPlaying is real (mode C), controls-suppressed by default.** The home NowPlaying card shows the the popover), with no regression to the hero/content.
6. **NowPlaying is real (mode C), with popover controls (full parity).** The home NowPlaying card shows the
*actual* high-res waveform of the playing track (scrolls/animates to the real signal, changes with track *actual* high-res waveform of the playing track (scrolls/animates to the real signal, changes with track
changes), sits at-rest when nothing plays, and mounts **controls-suppressed by default** (no knob bar, no changes), sits at-rest when nothing plays, and **carries the lava-lamp icon → popover panel like every
lava-lamp — pending §8b-followup). No hardcoded synthetic bars remain. other host** (§8b-followup resolved by the popover). No hardcoded synthetic bars remain.
7. **One engine, three modes.** Mix (mode A), the Cut/Session ambient layer (mode B), and the NowPlaying 7. **One popover-hosted control panel, placed everywhere.** There is exactly one
`WaveformVisualizerControls` panel and one `WaveformVisualizerControlPopover` host; Mix, Cut, Session, and
the NowPlaying card all place the *same* lava-lamp icon → popover and get the *identical* panel — verified
by no per-host control fork. The panel is styled to the NowPlaying Hero look (§3g) from theme tokens, no
hardcoded hex.
8. **One engine, three modes.** Mix (mode A), the Cut/Session ambient layer (mode B), and the NowPlaying
card (mode C) all consume the *same* `WaveformVisualizer` component + renderer + state — verified by card (mode C) all consume the *same* `WaveformVisualizer` component + renderer + state — verified by
there being exactly one of each, no per-host fork; the differences are hosting composition only there being exactly one of each, no per-host fork; the differences are hosting composition only
(full-bleed vs. `Ambient` slot vs. contained; viewport- vs. container-sized; controls present on all (full-bleed vs. `Ambient` slot vs. contained; viewport- vs. container-sized). Controls are uniform across
three detail hosts, suppressed-by-default on the contained home card). all four hosts (the single popover panel, criterion 7).
8. **Read-only everywhere.** No host (including the NowPlaying card) exposes a seek/scrub/transport via 9. **Read-only everywhere.** No host (including the NowPlaying card) exposes a seek/scrub/transport via
the visualizer; the read-only contract holds on every mount. the visualizer; the read-only contract holds on every mount. The popover panel exposes only the eight
9. **Bridge intact, re-keyed to track.** Single-owner handle, idempotent datum guard, `IsActivePlayer` tuning dials — no transport.
10. **Bridge intact, re-keyed to track.** Single-owner handle, idempotent datum guard, `IsActivePlayer`
gating, and the `isPlaying`-gated rAF loop are unchanged across all mounts; the datum guard now re-arms gating, and the `isPlaying`-gated rAF loop are unchanged across all mounts; the datum guard now re-arms
on **track** change (the multi-track Cut and the NowPlaying card both rely on this — they converge). on **track** change (the multi-track Cut and the NowPlaying card both rely on this — they converge).