docs(phase-12): spec waveform-visualizer generalization + NowPlayingHero rewire

Generalize the Mix-only WebGL lava visualizer into one release-cardinal
WaveformVisualizer serving Mix detail, all Release Detail pages, and the
home NowPlaying card. Four waves; flags the non-Mix datum-resolution call.
This commit is contained in:
daniel-c-harvey
2026-06-17 05:12:19 -04:00
parent ba1a1cd8ec
commit efef23753b
2 changed files with 521 additions and 0 deletions
+73
View File
@@ -254,6 +254,79 @@ Sequenced as **eight waves**; the critical path is `11.A → 11.B → 11.C → 1
---
## Phase 12 — Waveform Visualizer Generalization + NowPlayingHero Rewire
Take the landed Mix waveform visualizer (the WebGL2 lava renderer + its seven-knob controls, Phase 10
reframe) and **make it the one release-cardinal visualizer** — serving Mix detail, all Release Detail
pages, *and* the home-page NowPlaying card — instead of a Mix-only backdrop forked three ways. **Two
deliverables, one engine, DRY/SOLID the explicit ask.** Full design, the extraction analysis, the datum
decision, wave decomposition, and open questions: `product-notes/phase-12-waveform-visualizer-generalization.md`.
**Central finding (verified read, 2026-06-17): the engine is already release-cardinal below the surface.**
`MixWaveformVisualizer`'s bridge keys on `ReleaseEntryKey` + `TrackId` (not Mix); the renderer is a pure
function of a loudness datum + duration; the controls/state are renderer-agnostic. The *only* genuinely
Mix-coupled surface is (1) the datum **fetch** (`GET api/release/{entryKey}/mix/waveform` 404s unless
`Medium == Mix`) and (2) the high-res datum **source** (the `mix-waveforms` vault, Mix-only). Everything
else is just *named* `Mix*`. So "generalize from Mix to all releases" is a **rename + a data-source
generalization, not a rebuild** — the renderer, bridge, controls, read-only contract all carry forward
from the Phase 10 reframe unchanged.
**Crucial data fact (verified): non-Mix releases are not a data gap.** *Every* uploaded track already gets
a **512-bucket** waveform profile (`UnifiedTrackService.UploadAsync``waveform-profiles` vault, the
datum the player-bar `WaveformSeeker` consumes). Mixes *additionally* get a duration-derived **high-res**
datum (~333 samples/sec, `mix-waveforms` vault, CMS-triggered). So the only question for non-Mix is
*resolution*, not existence — and §8a frames that as the one real product call (recommend: serve the
existing 512-bucket profile for ambient non-Mix backdrops in v1; high-res-for-all is a roadmap upgrade).
**Deliverable 2 — NowPlayingHero overhaul.** `NowPlayingCard.razor` today animates **20 hardcoded
CSS-bounce bars** with no audio coupling (the "stochastic" visualizer). Replace them with the *same*
`WaveformVisualizer`, mounted inside the existing player cascade and pointed at "whatever is playing right
now" — so the home card shows the **real** waveform of the live track, Mix or not. The payoff of the
generalization: the NowPlaying card is *just another host* of the one engine, not a fork. The one genuine
engineering wrinkle is that the renderer assumes full-viewport (`position: fixed; inset: 0`,
clip-to-footer) and the card needs it container-relative — recommend a `Fill` mode parameter (spec §6c).
**Design discipline.** Rename the engine to its abstraction (`MixWaveformVisualizer``WaveformVisualizer`,
etc.) — a `Mix`-named component on a Cut page is a lie that cements the wrong model. Variance rides
**composition** (a new optional `Backdrop` slot on `ReleaseDetailScaffold`; 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, Phase 9 §5.3). The lava controls stay a **Mix affordance by default**;
Cut/Session (if they get a backdrop) mount it controls-suppressed as ambient — the seam supports controls-
everywhere later with no engine change (memory *Design for adaptability up front*).
Sequenced as **four waves**: `12.A → 12.B → (12.C ‖ 12.D)`.
- **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
prerequisite** — every later wave references the generalized names. Acceptance: Mix detail identical;
diff is identifiers only.
- **12.B — Generalize the datum fetch + endpoint.** New unauthenticated `GET
api/release/{entryKey}/waveform` resolving the **best-available** datum (high-res for Mix-with-datum;
512-bucket per-track profile otherwise); `IReleaseDataService.GetReleaseWaveform`; bridge calls it
instead of `GetMixWaveform`. **Depends on 12.A.** Direction A (recommended) needs no decision to start;
the high-res-for-all alternative (Direction B) does — §8a.
- **12.C — `Backdrop` slot on `ReleaseDetailScaffold` + mount on detail pages.** Promote the full-bleed /
foreground-stacking / dynamic-footer-clip pattern into the scaffold as an optional `Backdrop` slot; Mix
re-expresses its current mount through it; Cut mounts the controls-suppressed ambient backdrop; Session
mounts directly (it doesn't compose the scaffold — spec §3e) **if** §8b opts non-Mix in. **Depends on
12.B.**
- **12.D — NowPlayingHero rewire.** Replace the synthetic bars with a contained, controls-suppressed
`<WaveformVisualizer>` driven by the live cascaded player; add the `Fill`/container-sizing mode (spec
§6c). **Depends on 12.A + 12.B; independent of 12.C** (different host). Acceptance: home card shows the
real playing-track waveform, at-rest when nothing plays; no synthetic bars remain.
**Open product decisions (spec §8, need Daniel before the dependent wave):** (a) **non-Mix datum
resolution** — existing 512-bucket per-track profile (Direction A, recommended, zero new compute) vs.
high-res-for-all (Direction B, new CMS/API/content + backfill); gates 12.B's *richness*, blocks nothing if
A. (b) **Do Cut/Session get a backdrop, and with controls?** — recommend ambient-on-all-media /
controls-Mix-only; note even Mix-only still wants 12.A/B/D for the NowPlaying win. (c) **What is a
multi-track Cut's waveform?** — recommend first track by `TrackNumber` for v1; only bites if (b) gives Cut
a backdrop. (d) **NowPlaying container-sizing + home-page perf** — staff-engineer-owned (`Fill` mode;
`isPlaying`-gated rAF means an idle home page pays nothing), flagged so a lava lamp on the landing page is
no surprise.
---
## Working with this file
- **Add items by extending an existing phase first**; only create a new phase when the addition genuinely doesn't fit any of 15. Phase numbers are organisational, not sequencing.