docs: resolve Wave 8 open questions, add 8.L name consolidation, finalize 8.K visualizer design
This commit is contained in:
@@ -169,7 +169,9 @@ Waves 1–7 are landed (`COMPLETED.md §9`). Wave 6 closes two functional gaps a
|
|||||||
|
|
||||||
Daniel tested the landed Phase 9 surface end-to-end and produced a punch-list of corrections before the phase is called complete. These are **not new features** — they are the gap between what the Wave 1–7 specs *built* and what hands-on use *wants*. The theme is the same one Phase 9 has carried throughout: the medium taxonomy reaching every surface it should, and the browse surfaces matching the mental model rather than the implementation's first cut.
|
Daniel tested the landed Phase 9 surface end-to-end and produced a punch-list of corrections before the phase is called complete. These are **not new features** — they are the gap between what the Wave 1–7 specs *built* and what hands-on use *wants*. The theme is the same one Phase 9 has carried throughout: the medium taxonomy reaching every surface it should, and the browse surfaces matching the mental model rather than the implementation's first cut.
|
||||||
|
|
||||||
Two surfaces dominate: the **CMS Release Archive** (the card-grid landing is the wrong shape — Daniel wants medium *tabs*, not navigate-away cards) and the **public Archive** (the three-card overview is dead weight; the searchable all-releases view should *be* the archive). A third item — the **Mix Visualizer redesign** — is explicitly **not specced here**: Daniel wants to be interviewed first. It is carried as a design-pending track with an interview question set in `product-notes/phase-9-mix-visualizer-redesign.md`.
|
Two surfaces dominate: the **CMS Release Archive** (the card-grid landing is the wrong shape — Daniel wants medium *tabs*, not navigate-away cards) and the **public Archive** (the three-card overview is dead weight; the searchable all-**releases** view *is* the archive — release-cardinal, decided). The **Mix Visualizer redesign (8.K)** is **pulled out of Phase-9-completion scope** — Phase 9 closes without it — but is now **documented in full**: the interview ran and `product-notes/phase-9-mix-visualizer-redesign.md` is a finished, implementation-ready design spec for a post-Phase-9 wave.
|
||||||
|
|
||||||
|
**Open questions resolved (Daniel, 2026-06-13):** 8.H is decided **H2** (a new release-cardinal searchable browser at `/archive`; cascade: `/tracks` demoted from nav, route kept; mobile ARCHIVE → the browser; three-card overview fully retired); 8.I drops GENRES from the nav only (route kept); 8.F makes the Session hero optional-but-warn-if-missing; 8.E defaults the `ALL`-tab Add Track to Cut with the medium selector staying user-changeable. A new track **8.L** consolidates the release-name/track-name pair into a single name for single-track media.
|
||||||
|
|
||||||
Full track decomposition, acceptance criteria, and parallel/dependent analysis: `product-notes/phase-9-wave-8-remediation.md`. The tracks in brief:
|
Full track decomposition, acceptance criteria, and parallel/dependent analysis: `product-notes/phase-9-wave-8-remediation.md`. The tracks in brief:
|
||||||
|
|
||||||
@@ -178,19 +180,20 @@ Full track decomposition, acceptance criteria, and parallel/dependent analysis:
|
|||||||
- **8.B — `ALL` tab: all-releases grid with edit.** Left-most tab showing the current cross-medium releases grid with working edit buttons — the surface the retired Releases toggle used to show.
|
- **8.B — `ALL` tab: all-releases grid with edit.** Left-most tab showing the current cross-medium releases grid with working edit buttons — the surface the retired Releases toggle used to show.
|
||||||
- **8.C — Per-medium grids gain working edit affordances.** Cut / Session / Mix tab grids each get an Edit action routing to the correct edit page for that medium. *(Parallel with 8.D, 8.E once 8.A lands.)*
|
- **8.C — Per-medium grids gain working edit affordances.** Cut / Session / Mix tab grids each get an Edit action routing to the correct edit page for that medium. *(Parallel with 8.D, 8.E once 8.A lands.)*
|
||||||
- **8.D — Type chip reads "Session" / "DJ Mix" for non-Cuts.** The cross-medium grid's Type column must not show a Cut-only `ReleaseType` chip for Session/Mix rows. *(Independent.)*
|
- **8.D — Type chip reads "Session" / "DJ Mix" for non-Cuts.** The cross-medium grid's Type column must not show a Cut-only `ReleaseType` chip for Session/Mix rows. *(Independent.)*
|
||||||
- **8.E — Add-Track buttons in all modes, medium-aware routing.** Every tab surfaces an Add Track button routing to the upload page pre-set to that tab's medium. *(Depends on 8.A.)*
|
- **8.E — Add-Track buttons in all modes, medium-aware routing.** Every tab surfaces an Add Track button routing to the upload page pre-set to that tab's medium. The `ALL`-tab Add Track defaults to **Cut**; the medium selector stays user-changeable after landing on the form. *(Depends on 8.A.)*
|
||||||
- **8.F — Session hero image in the upload form (retire the two-step).** Compose the hero-image field into the Session upload form so a Session is authored in one pass; remove the "set it later from the browser" alert. *(Independent of the tab work; touches the upload form + the resource-addressed hero endpoint ordering — see note.)*
|
- **8.F — Session hero image in the upload form (retire the two-step).** Compose the hero-image field into the Session upload form so a Session is authored in one pass; remove the "set it later from the browser" alert. Hero is **optional but warns if missing** (no hard gate). *(Independent of the tab work; touches the upload form + the resource-addressed hero endpoint ordering — see note.)*
|
||||||
- **8.G — "Album Name" → "Release Name" label.** Rename the `AlbumHeaderFields` label. *(Independent, trivial.)*
|
- **8.G — "Album Name" → "Release Name" label.** Rename the `AlbumHeaderFields` label. *(Independent, trivial. Sequence before 8.L.)*
|
||||||
|
- **8.L — Consolidate release name + track name for single-track releases.** For Session/Mix the form presents **one** name field (Release Name); the underlying track name is derived from it (recommended: kept synced on create and edit so they never diverge). Cuts (multi-track) are unaffected. Blast radius (discovery done): CMS `BatchUpload` single-track branch, `BatchEdit` via `BatchTrackDetail`, and the legacy `TrackNew`/`TrackEdit` forms *if still live*; the public detail/gallery views already key off the release title only (no public work). *(Pairs with 8.G/8.E/8.F — same upload/edit forms.)*
|
||||||
|
|
||||||
**Public site (`DeepDrftPublic.Client`):**
|
**Public site (`DeepDrftPublic.Client`):**
|
||||||
- **8.H — Archive page becomes the searchable all-releases browser.** Retarget `/archive` from the three-card overview to the searchable all-releases view; this *is* the archive. Resolve the track-vs-release framing (see open question). *(Depends on the framing decision; see note.)*
|
- **8.H — Archive page becomes the searchable all-releases browser (release-cardinal, decided H2).** Build a new release-cardinal searchable browser at `/archive` (search + medium/genre filter, cards → per-medium detail); retire the three-card overview on every breakpoint. Cascade: `/tracks` (`TracksView`) is demoted from the nav (route kept reachable); mobile ARCHIVE → the new browser. *(Gates 8.I.)*
|
||||||
- **8.I — Nav slimmed: ARCHIVE + three medium modes inline, GENRES removed.** Above the medium breakpoint the appbar carries ARCHIVE (all-releases browser) and the three medium links directly; GENRES is eliminated from the nav. *(Depends on 8.H for the ARCHIVE target.)*
|
- **8.I — Nav slimmed: ARCHIVE + three medium modes inline, GENRES removed.** Above the medium breakpoint the appbar carries ARCHIVE (the new release-cardinal browser) and the three medium links directly; **GENRES is removed from the nav only** (route + `GenresView` kept reachable). *(Depends on 8.H for the ARCHIVE target.)*
|
||||||
- **8.J — ARCHIVE popover click does not close (bug).** Clicking a popover child leaves the pure-CSS hover dropdown stuck open on SPA navigation. Fix the dismissal. *(Independent bug fix.)*
|
- **8.J — ARCHIVE popover click does not close (bug).** Clicking a popover child leaves the pure-CSS hover dropdown stuck open on SPA navigation. Fix the dismissal. *(Independent bug fix.)*
|
||||||
|
|
||||||
**Mix Visualizer:**
|
**Mix Visualizer — out of Phase-9-completion scope:**
|
||||||
- **8.K — Mix Visualizer redesign. `[design pending interview]`.** Daniel wants a scrolling high-resolution waveform (bottom-to-top) with a slider coupling scroll-speed to zoom/resolution. He has **explicitly asked to be interviewed before this is designed.** No implementation spec exists or should be written until the interview runs. Question set: `product-notes/phase-9-mix-visualizer-redesign.md`.
|
- **8.K — Mix Visualizer redesign. `[post-Phase-9, design-complete]`.** A windowed, playback-coupled, bottom-to-top scrolling waveform showing the currently-playing region; zoom couples to apparent scroll speed (Guitar-Hero model, anchored at 1 quarter note @ 180 BPM = 333 ms visible at max zoom); lava-lamp aesthetic (theme-aware gradients, glassy), **strictly read-only**; standard Canvas/WebGL, no tricks, well-commented. The datum analysis recommends switching the Mix loudness profile from a fixed 2048 buckets to **constant-time-resolution capture (~333 samples/sec)** so long mixes aren't under-sampled at max zoom. **Phase 9 closes without this**; it runs as a post-Phase-9 wave, dispatchable straight from the finished spec: `product-notes/phase-9-mix-visualizer-redesign.md`.
|
||||||
|
|
||||||
**Dependency shape:** 8.B is the foundation for the CMS tab work (8.A consumes the shared grid; 8.C/8.E layer on once 8.A lands). 8.D, 8.F, 8.G are independent and parallelizable immediately. On the public side, 8.J is an independent bug fix; 8.H gates 8.I and rides an open framing question; 8.K is blocked on the interview and must not start until it runs.
|
**Dependency shape:** 8.B is the foundation for the CMS tab work (8.A consumes the shared grid; 8.C/8.E layer on once 8.A lands). 8.D, 8.G are independent and parallelizable immediately; 8.L follows 8.G and coordinates with 8.E/8.F (same forms). On the public side, 8.J is an independent bug fix; 8.H (decided H2 — the new release-cardinal archive) gates 8.I. **Phase 9 completion = 8.A–8.J + 8.L landed; 8.K is explicitly excluded** and runs as a separate post-Phase-9 wave.
|
||||||
|
|
||||||
|
|
||||||
## Working with this file
|
## Working with this file
|
||||||
|
|||||||
@@ -1,142 +1,331 @@
|
|||||||
# Phase 9 — Wave 8.K: Mix Visualizer Redesign (Interview Question Set)
|
# Phase 9 — 8.K: Mix Visualizer Redesign (Design Spec)
|
||||||
|
|
||||||
Status: **design pending interview**. Author: product-designer. Date: 2026-06-13.
|
Status: **design-complete, post-Phase-9.** Author: product-designer. Date: 2026-06-13
|
||||||
**No implementation spec exists or should be written until the interview runs.**
|
(interview answers captured 2026-06-13).
|
||||||
|
**Out of Phase-9-completion scope** — Phase 9 closes without this. This is an implementation-ready
|
||||||
|
spec; a future wave can be dispatched straight from it. **No code has been written by this doc.**
|
||||||
|
|
||||||
Cross-references: `PLAN.md §9.8` (Wave 8 entry, 8.K), `product-notes/phase-9-wave-8-remediation.md §4`,
|
Cross-references: `PLAN.md §9.8` (Wave 8 entry, 8.K — marked post-Phase-9), `product-notes/phase-9-wave-8-remediation.md §4`,
|
||||||
`product-notes/phase-9-release-medium-types.md §5.4` (the original `MixWaveformVisualizer` design).
|
`product-notes/phase-9-release-medium-types.md §5.4` (the original `MixWaveformVisualizer` design),
|
||||||
|
`DeepDrftAPI/Services/UnifiedReleaseService.cs` (the `MixWaveformBucketCount = 2048` compute),
|
||||||
|
`DeepDrftContent/Processors/WaveformProfileService.cs` (the datum compute + storage).
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Purpose
|
## Purpose
|
||||||
|
|
||||||
Daniel wants the Mix Visualizer **completely redesigned** and has **explicitly asked to be interviewed**
|
Daniel wants the Mix Visualizer **completely redesigned** from the current static silhouette into a
|
||||||
before any design is committed. This document is the structured question set the-boss relays to Daniel
|
**scrolling, playback-coupled waveform** — a musical score going by, bit by bit. He was interviewed
|
||||||
to run that interview. It is **not** a spec. When the interview produces answers, they get captured here
|
before any design was committed; this document is the captured result. It is a finished spec, not a
|
||||||
(or in a successor design note), and only then does 8.K become implementable.
|
question set.
|
||||||
|
|
||||||
|
One-line brief: **a windowed segment of the mix's waveform, showing only the currently-playing region,
|
||||||
|
scrolling bottom-to-top, coupled to playback, zoom-coupled to apparent scroll speed, rendered as a
|
||||||
|
theme-aware glassy lava-lamp background element, strictly read-only.**
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Current implementation (grounded, read 2026-06-13)
|
## Current implementation (grounded, read 2026-06-13)
|
||||||
|
|
||||||
So the questions are anchored in what exists rather than asked blind:
|
What exists today, so the redesign is anchored in the real starting point:
|
||||||
|
|
||||||
- **Component:** `MixWaveformVisualizer.razor` + `.razor.cs` in `DeepDrftPublic.Client/Controls/`.
|
- **Component:** `MixWaveformVisualizer.razor` + `.razor.cs` in `DeepDrftPublic.Client/Controls/`.
|
||||||
- **What it renders today:** a **static** full-viewport background. It fetches a stored loudness profile
|
- **What it renders today:** a **static** full-viewport background. It fetches a stored loudness profile
|
||||||
(`WaveformProfileDto`, base64 loudness bytes [0,255]) via `IReleaseDataService.GetMixWaveform(releaseId)`,
|
(`WaveformProfileDto`, base64 loudness bytes [0,255]) via `IReleaseDataService.GetMixWaveform(releaseId)`,
|
||||||
and builds **one closed SVG silhouette path** — a vertically mirrored continuous wave around the
|
and builds **one closed SVG silhouette path** — a vertically mirrored continuous wave around the
|
||||||
horizontal midline, stretched across the full viewport via `preserveAspectRatio="none"`. It is a
|
horizontal midline, stretched across the full viewport via `preserveAspectRatio="none"`. A single
|
||||||
single still shape; it does not move.
|
still shape; it does not move.
|
||||||
- **Layout:** rendered as the full-page background behind the Mix detail content
|
- **Layout:** full-page background behind the Mix detail content — `MixDetail.razor` places
|
||||||
(`MixDetail.razor` places `<MixWaveformVisualizer>` behind a `.mix-detail-foreground` stacking layer).
|
`<MixWaveformVisualizer>` behind a `.mix-detail-foreground` stacking layer.
|
||||||
- **Played-portion wash:** a `<rect>` clipped to the silhouette, width = `PlaybackPosition * width`,
|
- **Played-portion wash:** a `<rect>` clipped to the silhouette, width = `PlaybackPosition * width`,
|
||||||
washes the played portion. `PlaybackPosition` is a normalized [0,1] input.
|
washes the played portion. `PlaybackPosition` is a normalized [0,1] input.
|
||||||
- **Seek seam (inert):** `OnSeek` callback + two-way `PlaybackPosition` binding exist but click-to-seek
|
- **Inert seek seam:** `OnSeek` callback + two-way `PlaybackPosition` binding exist but click-to-seek is
|
||||||
is **not wired** — the seam was added for a future wave.
|
**not wired**. **This seam is now dropped from the design** — see §D (the redesign is read-only).
|
||||||
- **Data resolution:** the profile is the **high-resolution** Mix waveform datum computed server-side
|
- **Data:** the profile is the high-resolution Mix datum — a **fixed 2048-bucket** loudness profile
|
||||||
(§9.2.B trigger) and stored in the vault; distinct from the player-bar low-res peek.
|
(`UnifiedReleaseService.MixWaveformBucketCount = 2048`), computed server-side at upload from the
|
||||||
- **Explicit design boundary (from §5.4):** this component is deliberately **NOT** the player-bar
|
track's WAV (`WaveformProfileService.ComputeAndStoreAsync`) and stored in the `mix-waveforms` vault
|
||||||
peak-bar idiom (`SpectrumVisualizer` / `LevelMeterFab`). Those own the player bar; the Mix visualizer
|
keyed by the track's EntryKey. **Crucially: the bucket count is fixed at 2048 regardless of mix
|
||||||
has its own visual language.
|
length** — a 3-minute mix and a 90-minute mix both get exactly 2048 buckets. This is the load-bearing
|
||||||
|
constraint for §F.
|
||||||
**Daniel's seed idea for the redesign:** NOT a static background image. Instead the waveform **scrolls
|
- **Design boundary (from §5.4):** deliberately **NOT** the player-bar peak-bar idiom
|
||||||
from the bottom of the screen to the top** in **high resolution**, with a **slider controlling scroll
|
(`SpectrumVisualizer` / `LevelMeterFab`). Those own the player bar; the Mix visualizer has its own
|
||||||
speed / zoom level** — higher resolution moves faster. That is the entire brief so far; the interview
|
visual language. That boundary holds in the redesign.
|
||||||
fills in the rest.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Interview questions
|
## A. Motion model
|
||||||
|
|
||||||
Grouped by theme. Relay to Daniel; capture answers inline or in a successor note.
|
**The waveform scrolls like a musical score going by, bit by bit.** It is a **windowed segment showing
|
||||||
|
only the currently-playing region** — *not* the whole mix laid out and scrolled through, and *not* an
|
||||||
|
ambient free-running animation. The window is a moving slice of the mix centered on (or anchored to) the
|
||||||
|
playhead.
|
||||||
|
|
||||||
### A. Motion & scroll behaviour
|
- **Coupled to playback.** The scroll exists *because* the track is playing. Scroll position = playback
|
||||||
|
position. When playback pauses, the scroll holds (see §E for the idle/backgrounded behavior). When
|
||||||
1. The waveform scrolls **bottom-to-top**. Is it the *whole mix's* waveform scrolling past (like a
|
nothing is playing, there is no scroll — the panel shows a still slice (or the at-rest window at
|
||||||
scrolling score / piano-roll), or a *windowed* segment around the playback head? I.e. does the
|
position 0).
|
||||||
waveform represent the entire track laid out vertically and scroll through it, or a moving window?
|
- **Direction: bottom-to-top (scrolling up).** New audio enters from the bottom and flows upward;
|
||||||
2. Is scroll **coupled to playback** (the visualizer scrolls because the track is playing, position =
|
already-played audio exits off the top. This is fixed — confirmed intent, not a parameter.
|
||||||
playhead), or is it a **free ambient motion** independent of playback (scrolls even when paused /
|
- **"Now" anchor.** Because the window shows only the currently-playing region, the playhead sits at a
|
||||||
nothing is playing)? Or both modes?
|
fixed line within the window and the waveform flows past it. **Recommendation: place "now" at or near
|
||||||
3. If coupled to playback: does the **current playback position** sit at a fixed point on screen (e.g.
|
the vertical center** of the visible window, so the listener sees a short lead-in (audio about to
|
||||||
always centre, or always at the top "now" line) with the waveform flowing past it? Where is "now"?
|
play, below center) and a short trail-out (just-played audio, above center). This reads most like a
|
||||||
4. What happens at the **start and end** of the mix? Does it scroll in from empty / scroll out to empty,
|
score going by. (A top-anchored "now" line — everything visible is unplayed, flowing up to meet a line
|
||||||
loop, or hold?
|
at the top — is the alternative; center is the recommended default for the lava-lamp feel. Tunable.)
|
||||||
5. Direction is bottom-to-top — is that fixed, or is direction itself something to play with (some
|
- **Start and end of the mix.** At the very start, the window scrolls *in* from a partially-empty state
|
||||||
visualizers run top-down)? Confirm bottom-to-top is the intent.
|
(no audio below the lead-in yet); at the very end, it scrolls *out* to empty as the trail-out exits
|
||||||
|
the top. No looping, no hold-and-repeat — it begins and ends with the audio.
|
||||||
### B. Zoom / resolution coupling (the slider)
|
|
||||||
|
|
||||||
6. The slider couples **scroll speed and zoom/resolution** ("higher res moves faster"). Unpack the
|
|
||||||
coupling: does higher zoom mean (a) more waveform detail visible per unit height *and* faster scroll,
|
|
||||||
or (b) you're "zoomed in" on a shorter time-span so the same playback rate covers more screen, hence
|
|
||||||
faster apparent motion? These feel different — which is the mental model?
|
|
||||||
7. Is the slider a **single control** that ties speed and zoom together (one dimension), or do you want
|
|
||||||
**independent** control of zoom and speed (two sliders / a 2D control)?
|
|
||||||
8. What's the **range**? At minimum zoom, roughly how much of the mix is visible on screen (the whole
|
|
||||||
thing? a few minutes?); at maximum zoom, how fine (individual transients? bars/beats)?
|
|
||||||
9. Does the slider position **persist** across mixes / sessions, or reset each time? Is there a sensible
|
|
||||||
**default** zoom the page opens at?
|
|
||||||
10. Should the high-resolution datum support the deepest zoom you want, or is there a resolution ceiling
|
|
||||||
we should know about? (The stored datum has a fixed bucket count — extreme zoom may exceed its
|
|
||||||
resolution. Worth knowing the target so the datum resolution can be set to match.)
|
|
||||||
|
|
||||||
### C. Colour & aesthetics
|
|
||||||
|
|
||||||
11. What's the **visual feel** you're after — is this meant to be hypnotic/ambient (a lava-lamp you can
|
|
||||||
stare at), informational (read the structure of the mix), or both? What makes it "pleasing" to you?
|
|
||||||
12. **Colour treatment:** single colour, gradient, theme-aware (light/dark palette — "Charleston in the
|
|
||||||
Day" / "Lowcountry Summer Nights")? Should it react to anything (frequency, intensity, time)?
|
|
||||||
13. Does the **played vs. unplayed** distinction matter in the scrolling model the way the wash does
|
|
||||||
today? Or in a scroll-past model is "played" simply "already scrolled off the top"?
|
|
||||||
14. **Form of the wave:** keep the mirrored-silhouette filled shape, or something else — lines, bars,
|
|
||||||
particles, a denser spectral look? You said high-resolution; what does high-res *look* like to you?
|
|
||||||
15. Does it stay a **full-page background** behind the detail content (as today), or become a more
|
|
||||||
central/foreground element of the Mix detail page? Does the detail content (title, metadata, play
|
|
||||||
control) still sit over it?
|
|
||||||
|
|
||||||
### D. Interaction model
|
|
||||||
|
|
||||||
16. Is the visualizer **interactive**? The current build has an inert click-to-seek seam. Do you want
|
|
||||||
**click/scrub-to-seek** on the scrolling waveform — and if so, how does seeking interact with a
|
|
||||||
moving target (click a point as it scrolls past? scrub a position?)?
|
|
||||||
17. The slider is one control. Any **other controls** on the visualizer surface — play/pause, a
|
|
||||||
"follow playhead vs. free-scroll" toggle, anything?
|
|
||||||
18. On **touch / mobile**: does the scroll respond to touch gestures (drag to scrub, pinch to zoom), or
|
|
||||||
is it display-only on mobile with the slider as the only control?
|
|
||||||
19. Should the visualizer be **reusable** beyond the Mix detail page (the §5.4 brief made it a named
|
|
||||||
reusable component — e.g. a mix card preview, an embed)? Does the scrolling behaviour need to work
|
|
||||||
at small sizes, or is it a full-page-only treatment?
|
|
||||||
|
|
||||||
### E. Performance & technical constraints
|
|
||||||
|
|
||||||
20. Smooth bottom-to-top scrolling at high resolution is a **continuous animation** — likely Canvas or
|
|
||||||
WebGL rather than the current static SVG (SVG won't animate a high-res scroll smoothly). Are you
|
|
||||||
open to that rendering-tech shift, or is there a reason to stay SVG?
|
|
||||||
21. What's the **target experience** — buttery 60fps on desktop, with a graceful degrade on weaker
|
|
||||||
devices/mobile? Any device floor we should design to?
|
|
||||||
22. Does the scroll animation need to **keep running** while audio streams/decodes (the player is a
|
|
||||||
chunked streaming pipeline), or only animate once enough is buffered? Should it react to buffering
|
|
||||||
state at all?
|
|
||||||
23. Is there a **battery / ambient** concern — should it pause/slow when the tab is backgrounded or the
|
|
||||||
mix is paused, to avoid a CPU-hot idle animation?
|
|
||||||
|
|
||||||
### F. Scope & sequencing
|
|
||||||
|
|
||||||
24. Is this a **replace-in-place** of the current static visualizer (same data, same page slot, new
|
|
||||||
rendering), or does it pull in new data needs (e.g. higher-resolution datum, frequency/spectral data
|
|
||||||
the stored loudness profile doesn't carry)?
|
|
||||||
25. Is the scrolling visualizer a **must-ship for Phase 9 completion**, or can Phase 9 close with the
|
|
||||||
current static visualizer and the scroll redesign land as a fast-follow? (Affects whether 8.K blocks
|
|
||||||
calling Phase 9 done.)
|
|
||||||
26. Are there **references** — other visualizers, apps, videos — that capture the feel you want? A
|
|
||||||
concrete "like that, but…" anchors the design far better than abstract description.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## After the interview
|
## B. Zoom / resolution coupling — the Guitar Hero model
|
||||||
|
|
||||||
Capture Daniel's answers (here or in a successor design note), then this track converts from
|
**Mental model (b), confirmed:** zoom controls **how short a time-span fills the screen**. Zoomed in =
|
||||||
`[design pending interview]` to a real implementation spec — at which point the rendering-tech decision
|
a shorter span of audio occupies the full window height, so at a constant playback rate the audio
|
||||||
(question 20), the data-resolution question (10, 24), and the interaction model (16–18) are the three
|
traverses the window faster → **faster apparent scroll**. Zoomed out = a longer span fills the window →
|
||||||
things most likely to drive the build's shape and should be settled first.
|
slower apparent scroll. Daniel's analogy: **Guitar Hero** — higher difficulty is more "zoomed in," notes
|
||||||
|
appear and move faster.
|
||||||
|
|
||||||
|
This is a single coupled dimension: one zoom control drives both the visible time-span *and* (as a
|
||||||
|
consequence) the apparent scroll speed. There is **no independent speed control** — speed is a function
|
||||||
|
of zoom and the (fixed) playback rate.
|
||||||
|
|
||||||
|
### The hard anchor (load-bearing)
|
||||||
|
|
||||||
|
**At maximum zoom (fastest), exactly one quarter note is visible at 180 BPM.**
|
||||||
|
|
||||||
|
- One quarter note at 180 BPM = 60 / 180 s = **0.333 s (333 ms)** of audio.
|
||||||
|
- So the **most-zoomed window shows ~333 ms of audio**, top to bottom.
|
||||||
|
|
||||||
|
This anchors the *fast* end of the zoom range precisely. The *slow* end (minimum zoom — how much of the
|
||||||
|
mix is visible at most) is tunable; see the recommended default range below.
|
||||||
|
|
||||||
|
### Recommended default zoom range (smart guess now, tune later)
|
||||||
|
|
||||||
|
Daniel asked for a smart, aesthetically-pleasing default guess from current UI trends, range to be
|
||||||
|
tuned later. Recommendation:
|
||||||
|
|
||||||
|
- **Max zoom (anchor):** visible window = **0.333 s** (1 quarter note @ 180 BPM). This is the floor of
|
||||||
|
the time-span range.
|
||||||
|
- **Min zoom (default guess):** visible window = **~30 s**. A 30-second window scrolling up gives a calm,
|
||||||
|
readable "structure of the mix" view — you can see phrase-level shape without it feeling static. (For
|
||||||
|
reference: at 30 s the apparent scroll is ~90× slower than at the 0.333 s max-zoom end.)
|
||||||
|
- **Default opening zoom:** **~8–12 s visible.** Open the panel at a mid-calm zoom — enough motion to
|
||||||
|
read as alive (a lava lamp, not a frozen image), not so fast it reads as frantic. **Recommend 10 s** as
|
||||||
|
the default opening window. This is the "you glance at it and it's pleasantly drifting" setting.
|
||||||
|
- **Visible-window time-span range, then:** **0.333 s → 30 s** (a ~90× range), default open at **10 s**.
|
||||||
|
|
||||||
|
These are starting numbers chosen for feel; Daniel can tune the min-zoom ceiling and default opening
|
||||||
|
window once it's on screen. The **max-zoom 0.333 s anchor is fixed** (it's a stated requirement), and it
|
||||||
|
is what drives the datum-resolution analysis in §F.
|
||||||
|
|
||||||
|
### Slider persistence and default
|
||||||
|
|
||||||
|
- **Default:** open every Mix at the default opening zoom (~10 s window) — see above.
|
||||||
|
- **Persistence (recommendation):** persist the slider position **within a listening session** (so
|
||||||
|
scrubbing zoom on one mix carries to the next mix opened in the same session) but **reset to default on
|
||||||
|
a fresh page load**. This avoids a confusing "why is this mix zoomed weird" on return without making
|
||||||
|
the control feel forgetful mid-session. Low-stakes; tunable. (Cookie/localStorage if cross-session
|
||||||
|
persistence is later wanted.)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## C. Aesthetics — lava lamp, not test equipment
|
||||||
|
|
||||||
|
**Purely style and pleasure.** This is a **theming/background element**, not an informational readout.
|
||||||
|
The goal is hypnotic/ambient — something you can stare at — not "read the structure of the mix." If a
|
||||||
|
choice trades legibility-of-structure for beauty-of-motion, take beauty.
|
||||||
|
|
||||||
|
- **Theme-aware gradients.** The fill uses the active palette — **"Charleston in the Day"** (light) /
|
||||||
|
**"Lowcountry Summer Nights"** (dark) — as gradients, not flat color. It must respond to the dark-mode
|
||||||
|
toggle live (the same `DarkModeSettings` cascade the rest of the client uses). Pull gradient stops from
|
||||||
|
the MudBlazor palette so a palette change carries automatically.
|
||||||
|
- **Glassy treatment.** Frosted/translucent layering — think backdrop blur, soft luminous edges, a sense
|
||||||
|
of depth rather than a hard-edged silhouette. The waveform should feel like lit glass moving behind the
|
||||||
|
content, not a chart.
|
||||||
|
- **Form of the wave.** Keep a **filled, flowing shape** (the mirrored-silhouette lineage), but rendered
|
||||||
|
as a glassy gradient-filled band rather than a solid silhouette. High-resolution here means *smooth*
|
||||||
|
(no visible stair-stepping at any zoom), not *detailed-as-data*. The wave is a luminous ribbon flowing
|
||||||
|
upward.
|
||||||
|
- **Played vs. unplayed.** In a windowed, score-going-by model, "played" is simply "already scrolled off
|
||||||
|
the top." There is **no separate played-portion wash** like today's clipped rect — the motion itself
|
||||||
|
encodes progress. (Optionally, a subtle luminosity gradient across the window — brightest at the "now"
|
||||||
|
line, dimming toward the edges — can reinforce the playhead without a hard played/unplayed boundary.
|
||||||
|
Optional polish, not required.)
|
||||||
|
- **Layout.** Stays a **full-page background** behind the Mix detail content, as today — the detail
|
||||||
|
content (title, metadata, play control) sits over it via the existing `.mix-detail-foreground` stacking
|
||||||
|
layer. The redesign changes what's *in* the background, not where it sits.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## D. Interaction — strictly read-only
|
||||||
|
|
||||||
|
**NO SEEKING. The visualizer is strictly read-only.** It is a background/theming element, not an
|
||||||
|
interactive control. **Drop the inert click-to-seek seam** (`OnSeek`, the two-way `PlaybackPosition`
|
||||||
|
write-back) from the design — the redesigned component takes playback position as **one-way input only**
|
||||||
|
and never writes back.
|
||||||
|
|
||||||
|
- **No click-to-seek, no scrub, no controls on the panel.** The only thing that affects the visualizer
|
||||||
|
is (a) the playback position (input) and (b) the zoom slider — and even the zoom slider is a *viewing*
|
||||||
|
control, not a *playback* control.
|
||||||
|
- **Touch/mobile.** No touch gestures for seeking or scrubbing. The visualizer is display-only on
|
||||||
|
mobile. (Whether the zoom slider is exposed on mobile is a layout call — recommend yes, as a small
|
||||||
|
control, since it's the one knob; but it must never become a seek surface.)
|
||||||
|
- **Reusable/composable, but as a theme element.** The panel must remain a **reusable, composable
|
||||||
|
component** (give it a `ReleaseId` and a playback-position input, it renders itself — the current
|
||||||
|
component already self-fetches its datum, keep that). But it is integrated as a **theme/background
|
||||||
|
element**, not an interactive widget. If it's ever embedded elsewhere (a mix card preview, an embed),
|
||||||
|
it's still a read-only flowing backdrop. Design it so it works as a background at full-page size; small-
|
||||||
|
size embedding is a nice-to-have, not a requirement.
|
||||||
|
|
||||||
|
**Net contract change from today:** the component keeps a one-way `PlaybackPosition` input and a
|
||||||
|
`ReleaseId`; it **loses** `OnSeek` and the two-way write-back; it **gains** a zoom input (slider-bound)
|
||||||
|
and an internal animation loop.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## E. Performance & technical constraints
|
||||||
|
|
||||||
|
Daniel is **open to the rendering-tech shift** the scrolling animation requires — but with a hard
|
||||||
|
constraint: **NO TRICKS. Industry-standard patterns only, and comment the code well so Daniel can follow
|
||||||
|
it.** This is an explicit implementation constraint, not a preference.
|
||||||
|
|
||||||
|
- **Rendering tech.** A smooth, continuous bottom-to-top scroll at high resolution is a per-frame
|
||||||
|
animation; the current static SVG path won't animate smoothly. **Recommend HTML5 Canvas 2D** as the
|
||||||
|
default target: it is the industry-standard, well-documented, legible choice for a single flowing
|
||||||
|
waveform, it handles theme-aware gradients (`createLinearGradient`) and glassy compositing
|
||||||
|
(`globalAlpha`, `filter: blur()` / layered draws) directly, and it is far easier to comment and follow
|
||||||
|
than WebGL. **Reserve WebGL only if** Canvas 2D can't hold 60fps at the glassy treatment on target
|
||||||
|
devices — and if so, justify the move in a comment, and stay with standard, textbook WebGL (no exotic
|
||||||
|
shader tricks). Default: Canvas 2D.
|
||||||
|
- **Authoring:** follows the existing TypeScript-interop discipline (`DeepDrftPublic/Interop/audio/`
|
||||||
|
compiled to `wwwroot/js/`), one module per responsibility, consistent with the audio stack. The
|
||||||
|
visualizer animation module is new TS; the Blazor component drives it via a thin interop bridge
|
||||||
|
(pass datum + playback position + zoom; the TS owns the `requestAnimationFrame` loop).
|
||||||
|
- **No tricks, well-commented:** the scroll math (mapping playback time → window offset → which datum
|
||||||
|
samples are visible → screen Y), the zoom→time-span mapping, and the gradient/glass compositing each
|
||||||
|
get clear comments. Daniel must be able to read the module and follow how a quarter-note-at-180-BPM
|
||||||
|
becomes 333ms becomes N visible samples becomes pixels.
|
||||||
|
- **Frame budget.** Target **60fps on desktop**, graceful degrade on weaker devices/mobile (drop to a
|
||||||
|
lower internal sample density or a simpler gradient before dropping frames). No hard device floor set;
|
||||||
|
design to degrade, not to break.
|
||||||
|
- **Streaming interplay.** The audio player is a chunked streaming pipeline. The visualizer's datum is a
|
||||||
|
**separate, pre-computed, fully-downloaded** profile (not derived from the live stream) — so the scroll
|
||||||
|
animation does **not** depend on decode/buffer state and can run as soon as the datum is fetched and
|
||||||
|
playback position is flowing. It need not react to buffering. (If playback stalls on a buffer
|
||||||
|
underrun, the scroll holds because playback position holds — that falls out naturally from
|
||||||
|
playback-coupling.)
|
||||||
|
- **Idle / battery.** **Pause or slow the animation when the mix is paused or the tab is backgrounded**,
|
||||||
|
to avoid a CPU-hot idle animation. `requestAnimationFrame` already naturally throttles in a
|
||||||
|
backgrounded tab; additionally, gate the loop on "is playing" so a paused mix isn't burning frames.
|
||||||
|
This is the standard, no-tricks way to keep it cool.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## F. Data / datum resolution — the load-bearing analysis
|
||||||
|
|
||||||
|
Daniel's principle: **capture at a high enough resolution regardless of content length** — a long mix
|
||||||
|
must not be under-sampled by a fixed bucket count. This is a direct challenge to the current datum, which
|
||||||
|
is exactly a fixed bucket count. The question: does the stored datum suffice for the 333ms-at-max-zoom
|
||||||
|
requirement, or does it need to change?
|
||||||
|
|
||||||
|
### The current datum, measured against the anchor
|
||||||
|
|
||||||
|
The Mix datum is a **fixed 2048-bucket** loudness profile, content-length-agnostic
|
||||||
|
(`MixWaveformBucketCount = 2048`). So each bucket spans `mixDuration / 2048` of audio:
|
||||||
|
|
||||||
|
| Mix length | Seconds per bucket | Buckets in a 333 ms max-zoom window |
|
||||||
|
|------------|--------------------|--------------------------------------|
|
||||||
|
| 3 min (180 s) | 0.088 s | **~3.8 buckets** |
|
||||||
|
| 10 min (600 s) | 0.293 s | **~1.1 buckets** |
|
||||||
|
| 30 min (1800 s)| 0.879 s | **~0.38 buckets** |
|
||||||
|
| 60 min (3600 s)| 1.758 s | **~0.19 buckets** |
|
||||||
|
| 90 min (5400 s)| 2.637 s | **~0.13 buckets** |
|
||||||
|
|
||||||
|
**Conclusion: the current fixed-2048 datum fails the requirement for any mix longer than a few minutes.**
|
||||||
|
At max zoom the window must render ~333 ms of audio smoothly. To draw a smooth filled curve across the
|
||||||
|
window you want on the order of **60–120 sample points** in that window. The current datum delivers
|
||||||
|
*fractions of a single sample* for a typical (10–90 min) DJ mix — the max-zoom window would be a flat
|
||||||
|
line or a single interpolated segment. Even a short 3-minute mix gives only ~3.8 buckets in the window —
|
||||||
|
far short of smooth. The fixed bucket count is precisely the under-sampling-of-long-content failure
|
||||||
|
Daniel's principle warns against.
|
||||||
|
|
||||||
|
### Recommendation: switch to a content-length-aware (constant time-resolution) capture
|
||||||
|
|
||||||
|
**The stored datum should be captured at a constant *time* resolution, not a constant *bucket count*.**
|
||||||
|
Instead of "always 2048 buckets," capture "always N samples per second of audio" — so a 90-minute mix
|
||||||
|
gets proportionally more samples than a 3-minute one, and the time-resolution (seconds per sample) is the
|
||||||
|
same regardless of length. This is the direct expression of "high enough resolution regardless of content
|
||||||
|
length."
|
||||||
|
|
||||||
|
**Target sample density (concrete):**
|
||||||
|
|
||||||
|
- The max-zoom window is 333 ms. Target **~100 sample points across that window** for a smooth glassy
|
||||||
|
curve → `100 / 0.333 s` ≈ **300 samples/sec**.
|
||||||
|
- Round to a clean, defensible target: **~333 samples/sec ≈ one sample every 3 ms.** (333/sec makes the
|
||||||
|
333ms window hold exactly ~111 samples — comfortably smooth.)
|
||||||
|
- **State the target explicitly: capture the Mix loudness datum at ≈ 333 samples/second (≈ 3 ms/sample),
|
||||||
|
constant across all mix lengths.**
|
||||||
|
|
||||||
|
**What that costs (datum size):**
|
||||||
|
|
||||||
|
| Mix length | Samples @ 333/s | Bytes (1 byte/sample, current quantization) |
|
||||||
|
|------------|------------------|----------------------------------------------|
|
||||||
|
| 10 min | ~200,000 | ~200 KB |
|
||||||
|
| 30 min | ~600,000 | ~600 KB |
|
||||||
|
| 60 min | ~1,200,000| ~1.2 MB |
|
||||||
|
| 90 min | ~1,800,000| ~1.8 MB |
|
||||||
|
|
||||||
|
These are tractable as a one-time downloaded datum (the player already streams multi-megabyte audio; a
|
||||||
|
~1MB profile fetched once per mix detail page is fine). If size becomes a concern, two standard,
|
||||||
|
no-tricks mitigations:
|
||||||
|
|
||||||
|
1. **Cap + floor.** Capture at 333/s but cap the absolute sample count for extreme outliers (e.g. cap at
|
||||||
|
~2M samples ≈ a 100-min mix), accepting slightly-below-target density only past that length.
|
||||||
|
2. **Tiered / multi-resolution datum (mipmap-style).** Store the high-density datum *plus* a coarse
|
||||||
|
overview (e.g. the existing 2048-bucket profile) and let the renderer pick the right tier for the
|
||||||
|
current zoom — high-density only when zoomed in, coarse when zoomed out. This is the textbook approach
|
||||||
|
(it's how audio editors render waveforms at varying zoom) and stays "industry-standard, no tricks." It
|
||||||
|
also keeps the zoomed-*out* (30s window) view cheap. **Recommended if datum size is a concern;
|
||||||
|
otherwise the single high-density datum is simpler.**
|
||||||
|
|
||||||
|
**Compute-side change (for the future implementation wave):** `WaveformProfileService.ComputeAndStoreAsync`
|
||||||
|
already takes a `bucketCount` parameter, and `UnifiedReleaseService` already passes a Mix-specific value
|
||||||
|
(2048). The change is to compute the Mix bucket count **from the audio duration** (`bucketCount =
|
||||||
|
ceil(durationSeconds * 333)`) instead of a constant, optionally capped per above. The storage format,
|
||||||
|
vault, wire DTO (`WaveformProfileDto` — `BucketCount` + base64 `Data`), and fetch path **do not change** —
|
||||||
|
`BucketCount` simply becomes variable, which the DTO already supports (it's just an int). This is a
|
||||||
|
contained, backward-compatible datum change: existing 2048-bucket mixes still render (coarsely at max
|
||||||
|
zoom); re-running the generate trigger re-captures at the new density. **This datum change is part of the
|
||||||
|
8.K implementation wave, not a Phase 9 deliverable.**
|
||||||
|
|
||||||
|
### Summary of the datum recommendation
|
||||||
|
|
||||||
|
- **Current fixed-2048 datum: insufficient** for the 333ms-at-max-zoom anchor on any real-length mix.
|
||||||
|
- **Switch to constant-time-resolution capture at ≈ 333 samples/sec (≈ 3 ms/sample)**, content-length-aware.
|
||||||
|
- **Datum size** ~1.2 MB for a 60-min mix — tractable; use a **tiered/mipmap datum** if size or
|
||||||
|
zoomed-out cost matters.
|
||||||
|
- **No wire/format change** — only the bucket count becomes duration-derived and variable.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## G. Scope & sequencing
|
||||||
|
|
||||||
|
- **Out of Phase-9-completion scope.** Phase 9 closes without 8.K. This is a **post-Phase-9
|
||||||
|
implementation wave**, dispatchable straight from this spec.
|
||||||
|
- **This is a data-needs-change, not a replace-in-place.** Beyond the new rendering, it requires the
|
||||||
|
datum-resolution change in §F (duration-derived capture). Sequence the datum change first (or together)
|
||||||
|
— the renderer is only as good as the samples it's fed.
|
||||||
|
- **The three things that drive the build's shape, settle-first order:**
|
||||||
|
1. **Datum resolution (§F)** — switch to constant-time-resolution capture. Everything visual depends on
|
||||||
|
having enough samples in the max-zoom window.
|
||||||
|
2. **Rendering tech (§E)** — Canvas 2D (default), TS-interop module owning the `rAF` loop, well
|
||||||
|
commented, no tricks.
|
||||||
|
3. **Motion + zoom mapping (§A, §B)** — the windowed bottom-to-top scroll and the Guitar-Hero
|
||||||
|
zoom→time-span→apparent-speed coupling, anchored at 333ms max zoom.
|
||||||
|
- **Read-only (§D)** simplifies the build: no seek wiring, no gesture handling — drop the existing inert
|
||||||
|
seam.
|
||||||
|
|
||||||
|
**This spec is complete enough to dispatch an implementation wave.** Open items are tuning knobs (exact
|
||||||
|
min-zoom ceiling, default opening window, "now" anchor position, slider persistence scope, single vs.
|
||||||
|
tiered datum) — none block starting; all are called out inline with recommended defaults.
|
||||||
|
|||||||
@@ -1,13 +1,20 @@
|
|||||||
# Phase 9 — Wave 8: Remediation
|
# Phase 9 — Wave 8: Remediation
|
||||||
|
|
||||||
Status: spec (CMS + public tracks) / **design-pending-interview** (Mix Visualizer, 8.K).
|
Status: **spec** (CMS + public tracks — open questions resolved 2026-06-13).
|
||||||
Author: product-designer. Date: 2026-06-13.
|
Author: product-designer. Date: 2026-06-13.
|
||||||
**Plan only — no code edits made by this doc.**
|
**Plan only — no code edits made by this doc.**
|
||||||
|
|
||||||
Cross-references: `PLAN.md §9.8` (the concise Wave 8 entry), `COMPLETED.md §9.1–9.7`
|
Cross-references: `PLAN.md §9.8` (the concise Wave 8 entry), `COMPLETED.md §9.1–9.7`
|
||||||
(the landed Phase 9 waves this remediates), `product-notes/phase-9-release-medium-types.md`
|
(the landed Phase 9 waves this remediates), `product-notes/phase-9-release-medium-types.md`
|
||||||
(the originating design — §3 CMS surface, §5 public surface), `product-notes/phase-9-mix-visualizer-redesign.md`
|
(the originating design — §3 CMS surface, §5 public surface), `product-notes/phase-9-mix-visualizer-redesign.md`
|
||||||
(the 8.K interview question set), memory *One source, multiple views*.
|
(the 8.K finished design doc — **now a complete spec, pulled out of Phase-9-completion scope**),
|
||||||
|
memory *One source, multiple views*.
|
||||||
|
|
||||||
|
**Open questions are resolved (2026-06-13).** Daniel answered the whole decision list in §6 and ran
|
||||||
|
the 8.K interview. This doc no longer carries forks: 8.H is decided (H2 — a release-cardinal
|
||||||
|
all-releases browser at `/archive`), 8.I/8.F/8.E defaults are baked into their acceptance criteria, a
|
||||||
|
new consolidation track **8.L** is added, and **8.K is moved out of Phase-9-completion scope** to a
|
||||||
|
finished post-Phase-9 design doc. Phase 9 can close without 8.K.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -17,21 +24,23 @@ Daniel tested the landed Phase 9 surface (Waves 1–7, all on `dev`) and produce
|
|||||||
wave is the gap between what the specs *built* and what hands-on use *wants*. It is **remediation, not
|
wave is the gap between what the specs *built* and what hands-on use *wants*. It is **remediation, not
|
||||||
new feature work** — every item corrects or reshapes a surface that already exists.
|
new feature work** — every item corrects or reshapes a surface that already exists.
|
||||||
|
|
||||||
Three clusters:
|
Clusters:
|
||||||
|
|
||||||
1. **CMS Release Archive (8.A–8.E)** — the card-grid landing is the wrong shape. Daniel wants the
|
1. **CMS Release Archive (8.A–8.E)** — the card-grid landing is the wrong shape. Daniel wants the
|
||||||
medium varieties as **tab modes** that swap the grid in place, with an `ALL` tab on the left, and
|
medium varieties as **tab modes** that swap the grid in place, with an `ALL` tab on the left, and
|
||||||
working edit + add affordances in every mode. The current `ReleaseArchiveBrowser` (three cards that
|
working edit + add affordances in every mode. The current `ReleaseArchiveBrowser` (three cards that
|
||||||
navigate to separate `/tracks/sessions`, `/tracks/mixes` pages) is retired.
|
navigate to separate `/tracks/sessions`, `/tracks/mixes` pages) is retired.
|
||||||
2. **CMS upload/label polish (8.F–8.G)** — compose the Session hero image into the upload form (retire
|
2. **CMS upload/label polish (8.F–8.G, 8.L)** — compose the Session hero image into the upload form
|
||||||
the two-step), and rename "Album Name" → "Release Name."
|
(retire the two-step), rename "Album Name" → "Release Name," and **consolidate the
|
||||||
|
release-name/track-name pair into a single name for single-track media (Session/Mix)** so the admin
|
||||||
|
enters one name, not two.
|
||||||
3. **Public site (8.H–8.J)** — the three-card `/archive` overview is dead weight; the searchable
|
3. **Public site (8.H–8.J)** — the three-card `/archive` overview is dead weight; the searchable
|
||||||
all-releases view should *be* the archive. Slim the nav, drop GENRES, fix the stuck-open popover.
|
all-**releases** view (release-cardinal — decided H2) *is* the archive. Slim the nav, drop GENRES
|
||||||
|
(nav-only), fix the stuck-open popover.
|
||||||
|
|
||||||
**Not in this wave:** the **Mix Visualizer redesign (8.K)**. Daniel has explicitly asked to be
|
**Out of Phase-9-completion scope (but documented in full):** the **Mix Visualizer redesign (8.K)**.
|
||||||
interviewed before it is designed. It is carried as `[design pending interview]` with a structured
|
The interview has run; `phase-9-mix-visualizer-redesign.md` is now a complete, implementation-ready
|
||||||
question set in `phase-9-mix-visualizer-redesign.md`. No implementation spec exists or should be
|
design spec. Phase 9 closes without it; it runs as a post-Phase-9 wave.
|
||||||
authored until the interview runs.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -226,16 +235,18 @@ not a per-medium route fork.
|
|||||||
**Acceptance criteria.**
|
**Acceptance criteria.**
|
||||||
- Add Track is present on every tab (`ALL`, Cut, Session, Mix).
|
- Add Track is present on every tab (`ALL`, Cut, Session, Mix).
|
||||||
- Add Track on a medium tab opens the upload form with that medium pre-selected.
|
- Add Track on a medium tab opens the upload form with that medium pre-selected.
|
||||||
|
- **The `ALL` tab's Add Track defaults to Cut** (Daniel, 2026-06-13) — the existing default and most
|
||||||
|
common case.
|
||||||
|
- **The medium selector remains user-changeable after landing on the upload form** (Daniel,
|
||||||
|
2026-06-13). The pre-selected medium is a *starting point*, not a lock: pre-selecting Session from
|
||||||
|
the Session tab opens the form in Session mode, but the admin can still switch the selector to Cut/Mix
|
||||||
|
on the form without going back. The pre-selection seeds; it does not constrain.
|
||||||
- The pre-selected medium drives the conditional form fields immediately (Session shows the hero field
|
- The pre-selected medium drives the conditional form fields immediately (Session shows the hero field
|
||||||
per 8.F; Cut shows `ReleaseType`; etc.).
|
per 8.F; Cut shows `ReleaseType`; etc.), and switching the selector re-drives them live.
|
||||||
|
|
||||||
**Dependencies.** Depends on **8.A** (tabs exist to host the buttons). Pairs with **8.F** (a Session
|
**Dependencies.** Depends on **8.A** (tabs exist to host the buttons). Pairs with **8.F** (a Session
|
||||||
Add-Track that pre-selects Session should land the admin on a form that *has* the hero field).
|
Add-Track that pre-selects Session should land the admin on a form that *has* the hero field).
|
||||||
|
|
||||||
**Open question (minor, recommend a default).** What medium does the `ALL` tab's Add Track default to?
|
|
||||||
*Recommend Cut* (the existing default and the most common case); the admin can switch the selector. Not
|
|
||||||
worth blocking.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### 8.F — Session hero image in the upload form (retire the two-step)
|
### 8.F — Session hero image in the upload form (retire the two-step)
|
||||||
@@ -262,25 +273,30 @@ or in the `MediumFields` dispatch — staff-engineer's call — but the *user* s
|
|||||||
|
|
||||||
`SessionFields.razor`'s current body (a `MudAlert` only) is replaced by a real hero-image input.
|
`SessionFields.razor`'s current body (a `MudAlert` only) is replaced by a real hero-image input.
|
||||||
|
|
||||||
|
**Hero image — optional, but warn if missing (Daniel, 2026-06-13).** The hero image is **not** a hard
|
||||||
|
validation gate: a Session can be submitted without one and the upload succeeds. **But** the form
|
||||||
|
**surfaces a warning when a Session is submitted (or about to be submitted) without a hero image** — a
|
||||||
|
soft nudge, not a block. Rationale: a Session's hero is its primary visual identity on the public detail
|
||||||
|
page (it is the precedence-first image — see `SessionDetail.razor`), so a missing hero is *usually* an
|
||||||
|
oversight worth flagging, but Daniel wants the seed-then-correct path (set later via the browser) to
|
||||||
|
remain valid. So: warn, don't gate.
|
||||||
|
|
||||||
**Acceptance criteria.**
|
**Acceptance criteria.**
|
||||||
- The Session upload form presents a hero-image input (in addition to cover art).
|
- The Session upload form presents a hero-image input (in addition to cover art).
|
||||||
- Submitting a Session with a chosen hero image creates the release and sets `HeroImageEntryKey` in one
|
- Submitting a Session with a chosen hero image creates the release and sets `HeroImageEntryKey` in one
|
||||||
flow — no separate manual step required.
|
flow — no separate manual step required.
|
||||||
- The "set hero from the browser later" alert is removed.
|
- The "set hero from the browser later" alert is removed.
|
||||||
|
- **Submitting a Session with no hero image surfaces a warning** (e.g. an inline `MudAlert` of
|
||||||
|
`Severity.Warning`, or a confirm-dialog "Submit without a hero image?") — the submit still proceeds;
|
||||||
|
the warning informs, it does not block. No hard `Required` validation on the hero field.
|
||||||
- The per-row hero upload in `CmsSessionBrowser` still works as a replace/correct path.
|
- The per-row hero upload in `CmsSessionBrowser` still works as a replace/correct path.
|
||||||
- A Session uploaded with no hero image still succeeds (hero optional), and can have one set later via
|
- A Session uploaded with no hero image still succeeds and can have one set later via the browser
|
||||||
the browser (back-compat with the existing per-row path).
|
(back-compat with the existing per-row path).
|
||||||
|
|
||||||
**Dependencies.** Independent of the tab restructure (8.A–8.E). Touches the upload form and the submit
|
**Dependencies.** Independent of the tab restructure (8.A–8.E). Touches the upload form and the submit
|
||||||
sequencing. Pairs naturally with **8.E** (Session Add-Track should land on this improved form). Can be
|
sequencing. Pairs naturally with **8.E** (Session Add-Track should land on this improved form). Can be
|
||||||
built in parallel with the tab work.
|
built in parallel with the tab work.
|
||||||
|
|
||||||
**Open question (flag, recommend).** Is the hero image **required** for a Session, or optional? Daniel's
|
|
||||||
note says "the form should allow uploading all metadata including the hero image" — *allow*, not
|
|
||||||
*require*. **Recommend optional** (consistent with cover art being optional, and with the existing
|
|
||||||
per-row set-later path remaining valid). Confirm with Daniel; if required, the form gains a validation
|
|
||||||
gate.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### 8.G — "Album Name" → "Release Name" label
|
### 8.G — "Album Name" → "Release Name" label
|
||||||
@@ -303,6 +319,104 @@ should follow for consistency — e.g. placeholder/help text — but the named c
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
### 8.L — Consolidate release name + track name for single-track releases
|
||||||
|
|
||||||
|
**Goal.** For single-track media (**Session** and **Mix** — the §9.6/§9.7 one-track-per-medium
|
||||||
|
releases), the UI presents and stores **a single name**. The admin enters/sees one **"Release Name"**;
|
||||||
|
the track name is **derived from it automatically** — never entered or shown as a separate field. This
|
||||||
|
is a **consolidation**, not an addition: today these forms surface *two* name inputs (Release Name +
|
||||||
|
Track Name) for media that conceptually have only one name.
|
||||||
|
|
||||||
|
**Why.** A Session or Mix is a single work. There is exactly one thing to name. Surfacing a separate
|
||||||
|
"Track Name" alongside "Release Name" for these media is a redundant input that invites divergence
|
||||||
|
(the release titled "Lowcountry Live #3" whose lone track is named "untitled-master-final") and a
|
||||||
|
confusing authoring experience. Cuts are different — a Cut release legitimately has a release name
|
||||||
|
distinct from its per-track names ("Charleston EP" → tracks "Battery", "Rainbow Row") — and are
|
||||||
|
**unaffected** by this track.
|
||||||
|
|
||||||
|
**Recommendation — keep them synced for single-track media (Daniel to confirm posture; this is the
|
||||||
|
recommended default).** On both create and edit of a single-track release, the underlying track name is
|
||||||
|
**set equal to the Release Name on save, and kept in sync** so they can never diverge. The admin never
|
||||||
|
sees or touches the track name for Session/Mix. On edit, changing the Release Name updates the track
|
||||||
|
name with it. This is the simplest model and the one that honors "there is only one name": the track
|
||||||
|
name is a *derived field*, not an independent one. (The alternative — let them diverge once set — would
|
||||||
|
reintroduce exactly the two-name confusion this track removes. Rejected.)
|
||||||
|
|
||||||
|
**Discovery audit — every UI surface that surfaces separate Release vs. Track name (read against live
|
||||||
|
source, 2026-06-13).** This is the full blast radius. Implementation must collapse the name inputs on
|
||||||
|
the single-track path for each:
|
||||||
|
|
||||||
|
*CMS — the surfaces that show BOTH names today (these are the ones to fix):*
|
||||||
|
- **`BatchUpload.razor` (`/tracks/upload`) — the single-track branch.** When `_medium != Cut`, the form
|
||||||
|
renders `AlbumHeaderFields` (which carries the Release/Album name — renamed "Release Name" in 8.G)
|
||||||
|
**plus** a separate `<MudTextField Label="Track Name">` bound to `_tracks[0].TrackName` (lines ~62-65).
|
||||||
|
This is the primary offender on the create path. For Session/Mix this Track Name input must be
|
||||||
|
**removed**, and `_tracks[0].TrackName` set equal to the Release Name on submit.
|
||||||
|
- **`BatchEdit.razor` (`/tracks/album/{AlbumName}/edit`) — the single-track path.** Renders
|
||||||
|
`AlbumHeaderFields` (Release Name) **plus** `BatchTrackList` + `BatchTrackDetail`. For single-track
|
||||||
|
media the list is already collapsed to one row (`OnMediumChanged` / load-path trim), but
|
||||||
|
`BatchTrackDetail` still shows a separate `<MudTextField Label="Track Name">` (its lines ~8-15) for
|
||||||
|
that one row. On the single-track path this Track Name editor must be **suppressed**, and the row's
|
||||||
|
`TrackName` kept equal to `_albumName` on save.
|
||||||
|
- **`BatchTrackDetail.razor`** — the component that renders the "Track Name" field (lines ~8-15). It is
|
||||||
|
shared by the Cut path (where it stays) and the single-track path (where it must be hidden). Cleanest:
|
||||||
|
the parent passes a flag (e.g. `ShowTrackName` / `IsSingleTrack`) so `BatchTrackDetail` suppresses the
|
||||||
|
name field for single-track media while still showing the WAV/Original-File rows.
|
||||||
|
- **`TrackNew.razor` (`/tracks/new`) — the legacy single-track add form.** Shows `Track Name` (line 31),
|
||||||
|
`Album` (line 33), and a `MediumFields` selector. When the selected medium is single-track, the
|
||||||
|
separate Track Name vs. Album split is the same redundancy. This legacy form is a secondary surface
|
||||||
|
(the batch form is the primary upload path) — confirm whether it is still a live entry point before
|
||||||
|
investing; if it is, apply the same collapse (one name when medium is Session/Mix). If it is dead,
|
||||||
|
note it for retirement rather than re-plumbing.
|
||||||
|
- **`TrackEdit.razor` (`/tracks/{Id:long}`) — the legacy single-track edit form.** Shows `Track Name`
|
||||||
|
(line 46) and `Album` (line 58) as separate fields, with a `MediumFields` selector. Same disposition
|
||||||
|
as `TrackNew`: collapse on the single-track path if live, flag for retirement if not.
|
||||||
|
|
||||||
|
*CMS — surfaces that already do the right thing (no change needed, listed so implementation knows they
|
||||||
|
are clean):*
|
||||||
|
- **`CmsMediumTable.razor`** (the Sessions/Mixes browser grid) shows only `ReleaseAccessor(context).Title`
|
||||||
|
(the release name) as its title column — no separate track-name column. Clean.
|
||||||
|
- **`CmsSessionBrowser` / `CmsMixBrowser`** consume `CmsMediumTable` and likewise key off the release
|
||||||
|
title only. Clean.
|
||||||
|
|
||||||
|
*Public — surfaces that already do the right thing (no change needed):*
|
||||||
|
- **`SessionDetail.razor`** uses only `release.Title` for the masthead and `ViewModel.Track` for
|
||||||
|
playback — it never renders the track name separately. Clean.
|
||||||
|
- **`MixDetail.razor`** likewise uses only `release.Title` + `ViewModel.Track`. Clean.
|
||||||
|
- **`ReleaseGallery.razor`** (the Sessions/Mixes card grid) shows `release.Title` + `release.Artist`
|
||||||
|
only. Clean.
|
||||||
|
|
||||||
|
**Net blast radius:** the consolidation is **entirely CMS-side**, and concentrated in the two batch
|
||||||
|
forms (`BatchUpload`, `BatchEdit`) via the shared `BatchTrackDetail`, plus the two legacy single-track
|
||||||
|
forms (`TrackNew`, `TrackEdit`) *if they are still live*. The public site already treats a single-track
|
||||||
|
release as one-named — no public work. This is the discovery step Daniel asked for: implementation
|
||||||
|
knows the full surface set before touching anything.
|
||||||
|
|
||||||
|
**Acceptance criteria.**
|
||||||
|
- On the **create** path (batch upload, single-track medium): the form presents **one** name field
|
||||||
|
(Release Name). No separate Track Name input. On save, the single track's `TrackName` is set equal to
|
||||||
|
the Release Name.
|
||||||
|
- On the **edit** path (batch edit, single-track medium): the form presents **one** name field (Release
|
||||||
|
Name). The per-row Track Name editor is suppressed. On save, the track's `TrackName` is set equal to
|
||||||
|
the (possibly edited) Release Name — they stay synced.
|
||||||
|
- **Cuts (multi-track) are unaffected**: a Cut release keeps its Release Name distinct from per-track
|
||||||
|
names, and `BatchTrackDetail` still shows the per-track Track Name field for Cut rows.
|
||||||
|
- Switching the medium selector mid-form between Cut and a single-track medium re-drives which name
|
||||||
|
fields are visible (single name for Session/Mix; release + per-track names for Cut) without losing
|
||||||
|
entered data where it still applies.
|
||||||
|
- The legacy `TrackNew` / `TrackEdit` forms either apply the same single-name collapse (if live) or are
|
||||||
|
flagged for retirement (if dead) — staff-engineer confirms their live status during implementation.
|
||||||
|
- No public-site change is required (verified: public detail/gallery views already key off the release
|
||||||
|
title only).
|
||||||
|
|
||||||
|
**Dependencies.** Pairs with **8.G** ("Album Name" → "Release Name" — the single name field these forms
|
||||||
|
present should already read "Release Name" before this collapse lands; sequence 8.G first or together).
|
||||||
|
Touches the same upload/edit forms as **8.E**/**8.F** — coordinate so the Session form's hero input
|
||||||
|
(8.F), medium pre-selection (8.E), and name collapse (8.L) land coherently rather than fighting over the
|
||||||
|
same submit handler. Independent of the tab restructure (8.A–8.D) and the public cluster.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## 3. Public site — 8.H through 8.J
|
## 3. Public site — 8.H through 8.J
|
||||||
|
|
||||||
### 8.H — Archive page becomes the searchable all-releases browser
|
### 8.H — Archive page becomes the searchable all-releases browser
|
||||||
@@ -311,56 +425,56 @@ should follow for consistency — e.g. placeholder/help text — but the named c
|
|||||||
releases**. The searchable all-releases browser *is* the archive.
|
releases**. The searchable all-releases browser *is* the archive.
|
||||||
|
|
||||||
**User-visible change.** Visiting `/archive` (or clicking ARCHIVE) lands on a searchable, filterable
|
**User-visible change.** Visiting `/archive` (or clicking ARCHIVE) lands on a searchable, filterable
|
||||||
browser of all releases — not three static cards. The cards are gone; the archive is the browse
|
browser of **all releases** — not three static cards. The cards are gone; the archive is the browse
|
||||||
surface.
|
surface.
|
||||||
|
|
||||||
**Shape — and the framing tension that needs a decision.** Daniel's note says the Archive should be
|
**Decision (H2 — Daniel, 2026-06-13): the archive is release-cardinal.** Build a **new searchable
|
||||||
"what the TRACKS page links to now — the searchable view of all releases," and that "naming everything
|
all-*releases* browser at `/archive`** — search + medium/genre filter, release cards (cover, title,
|
||||||
TRACKS is misleading." There is a real cardinality mismatch to resolve:
|
artist) that link to the right per-medium detail page. This is consistent with the `/cuts`
|
||||||
|
release-cardinal model and the `api/release` read family, and it mirrors the CMS tab model (8.A): both
|
||||||
|
the public archive and the CMS archive now share one "all releases, filter by medium, search within"
|
||||||
|
mental model (*One source, multiple views*). The earlier H1 reading (relabel the track-cardinal
|
||||||
|
`TracksView` and re-home it) is **dropped** — Daniel thinks in releases, and the archive should match.
|
||||||
|
|
||||||
- The current `/tracks` (`TracksView`) is **track-cardinal** — it lists individual *tracks* with search
|
**The cascade from H2 (decided):**
|
||||||
and album/genre filter pills. It is the searchable view that exists today.
|
- **`/tracks` (`TracksView`, track-cardinal) is no longer the archive.** It is not what ARCHIVE points
|
||||||
- The medium browsers (`/cuts`, `/sessions`, `/mixes`) are **release-cardinal**.
|
at. Its fate: keep the route reachable (no hard 404), but it is demoted from the nav — the archive is
|
||||||
- Daniel says "all releases" — which reads release-cardinal — but points at `TracksView`, which is
|
the release-cardinal browser, and the flat track gallery is not the primary browse surface anymore.
|
||||||
track-cardinal.
|
Treat `/tracks` the same way 8.I treats `/genres`: **drop it from the nav, keep the route reachable**,
|
||||||
|
pending a later decision on whether to retire it wholesale. (It is not a Wave 8 deliverable to remove
|
||||||
|
it; it simply stops being the archive.)
|
||||||
|
- **8.I follows from H2** — ARCHIVE links to the new release-cardinal browser (not `TracksView`), and
|
||||||
|
the three medium links sit beside it.
|
||||||
|
|
||||||
Two readings, and Daniel should pick:
|
**Shape (informing, not prescribing).** A new public page at `/archive` consuming the `api/release`
|
||||||
|
paged-list family (which already supports a medium filter — `COMPLETED.md §9`). Reuse the
|
||||||
|
release-cardinal card idiom (`ReleaseGallery` already renders release cards that link to
|
||||||
|
`/{detailRoute}/{id}`); the archive adds a search field and a medium filter (ALL + per-medium) above the
|
||||||
|
grid. Drive the medium filter off `Enum.GetValues<ReleaseMedium>()` + a label lookup (the same Phase 9
|
||||||
|
extension discipline), so a fourth medium surfaces a filter chip for free. Card→detail routing is
|
||||||
|
medium-aware (a Cut card → `/cuts`-flavored target, a Session card → `/sessions/{id}`, a Mix card →
|
||||||
|
`/mixes/{id}`); reuse the existing per-medium detail routes.
|
||||||
|
|
||||||
- **(H1) Archive = the existing track gallery, renamed.** Move/retarget `TracksView`'s searchable
|
**Mobile ARCHIVE → the searchable browser (Daniel, 2026-06-13).** With `/archive` becoming the
|
||||||
gallery to `/archive`, drop the misleading "Tracks" naming, and treat the flat searchable list as the
|
searchable browser, **mobile ARCHIVE goes straight to that browser** (the medium modes are reachable
|
||||||
archive. Lowest effort — it is the view that exists, relabelled and re-homed. *But* it is
|
via the in-page medium filter). The medium links also live in the hamburger sub-list under ARCHIVE
|
||||||
track-cardinal, which sits oddly with "all releases" and with the release-cardinal medium views it
|
(8.I). **The three-card overview is fully retired** — there is no card-overview destination on any
|
||||||
sits beside.
|
breakpoint.
|
||||||
- **(H2) Archive = a new searchable all-*releases* browser.** Build a release-cardinal searchable
|
|
||||||
browser (search + medium/genre filter) at `/archive`, consistent with the `/cuts` release-cardinal
|
|
||||||
model and the `api/release` read family. More work (a new browse surface), but coherent: the archive
|
|
||||||
is releases, the medium tabs filter the archive, search filters within. This matches the *CMS* tab
|
|
||||||
model (8.A) — the public archive and the CMS archive would share the "all releases, filter by medium,
|
|
||||||
search within" mental model.
|
|
||||||
|
|
||||||
**Recommendation: (H2) if the release is the unit Daniel thinks in; (H1) if speed matters most.** The
|
**Acceptance criteria.**
|
||||||
CMS side (8.A) is moving to a release-cardinal, medium-filtered archive — symmetry argues for (H2) on
|
- `/archive` renders a **release-cardinal** searchable browse surface (search field + medium/genre
|
||||||
the public side too (*One source, multiple views*: the same browse model, CMS and public). But (H1) is
|
filter), not the three-card overview.
|
||||||
materially cheaper and may be all Daniel wants. **This is a product decision — flag for Daniel before
|
- The surface covers **all releases** (every medium), with a medium filter (ALL + per-medium) and search
|
||||||
building 8.H.** Do not pick by default; the cardinality choice cascades into 8.I (what ARCHIVE links to)
|
within.
|
||||||
and the fate of `/tracks`.
|
- Each release card links to the correct per-medium detail page.
|
||||||
|
- The medium filter is enum-driven (a fourth medium surfaces a filter chip with one lookup entry, no
|
||||||
|
markup fork).
|
||||||
|
- The old three-card overview (`ArchiveView`'s current body) is **fully retired** — desktop and mobile.
|
||||||
|
- `/tracks` (`TracksView`) is no longer the archive and is dropped from the nav (route remains
|
||||||
|
reachable; see 8.I posture for `/genres`).
|
||||||
|
|
||||||
**Acceptance criteria (conditional on the framing decision).**
|
**Dependencies.** Gates **8.I** (8.I links ARCHIVE to this browser). Independent of 8.J. No longer gated
|
||||||
- `/archive` renders a searchable browse surface (search + filter), not the three-card overview.
|
on a framing decision — H2 is decided.
|
||||||
- The surface covers all releases (H2) or all tracks (H1) per the decision.
|
|
||||||
- The old three-card overview is retired (or repurposed — see 8.I, the mobile question).
|
|
||||||
- The misleading "Tracks"-as-everything naming is resolved (the route/label reflects "archive").
|
|
||||||
|
|
||||||
**Dependencies.** **Gated on the framing decision above.** Gates **8.I** (8.I links ARCHIVE to whatever
|
|
||||||
8.H produces). Independent of 8.J.
|
|
||||||
|
|
||||||
**Open question (carry-over from §5.1 of the medium-types spec).** The original ARCHIVE design used
|
|
||||||
`/archive` as the *mobile* overview (three cards) since the desktop popover is hover-only. If `/archive`
|
|
||||||
becomes the searchable browser, **what is the mobile ARCHIVE destination?** Options: ARCHIVE on mobile
|
|
||||||
goes straight to the searchable browser (the three medium modes are reachable via in-page filter/tabs),
|
|
||||||
or the mobile hamburger keeps the three medium links indented under ARCHIVE (already does — see 8.I).
|
|
||||||
Recommend: mobile ARCHIVE → the searchable browser; the medium links live in the hamburger sub-list
|
|
||||||
(8.I) so the three-card overview is fully retired. Confirm with Daniel.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -387,23 +501,23 @@ the desktop breakpoint (though 8.J should still be fixed for any breakpoint wher
|
|||||||
e.g. mobile or a narrow-desktop fallback). Coordinate 8.I and 8.J: 8.I may *reduce* where the popover
|
e.g. mobile or a narrow-desktop fallback). Coordinate 8.I and 8.J: 8.I may *reduce* where the popover
|
||||||
exists, 8.J fixes the dismissal wherever it remains.
|
exists, 8.J fixes the dismissal wherever it remains.
|
||||||
|
|
||||||
`/genres` route and `GenresView` — Daniel says eliminate GENRES from the *nav*. Recommend the same
|
**GENRES — remove the nav link only, for now (Daniel, 2026-06-13).** Drop the GENRES menu item. **Keep
|
||||||
posture Phase 9 took with CMS genre browse: **drop the nav item, keep the route reachable** (no active
|
the `/genres` route and `GenresView` reachable** — no active development, no hard removal. This is the
|
||||||
development, no hard removal) unless Daniel says retire it wholesale. Flag.
|
same posture Phase 9 took with the CMS genre browse and the same posture H2 sets for `/tracks`: demote
|
||||||
|
from the nav, leave the route intact, defer the wholesale-retire question. So the work here is purely
|
||||||
|
*remove the menu item from `Pages.cs`* — `GenresView` itself is untouched.
|
||||||
|
|
||||||
**Acceptance criteria.**
|
**Acceptance criteria.**
|
||||||
- Above the medium breakpoint, ARCHIVE and CUTS / SESSIONS / MIXES appear as appbar links.
|
- Above the medium breakpoint, ARCHIVE and CUTS / SESSIONS / MIXES appear as appbar links.
|
||||||
- ARCHIVE links to the all-releases browser (8.H's output).
|
- ARCHIVE links to the all-releases browser (8.H's output — the release-cardinal `/archive`).
|
||||||
- Each medium link navigates to its view page (`/cuts`, `/sessions`, `/mixes`).
|
- Each medium link navigates to its view page (`/cuts`, `/sessions`, `/mixes`).
|
||||||
- GENRES no longer appears in the nav.
|
- **GENRES no longer appears in the nav; `/genres` + `GenresView` remain reachable by URL** (nav-only
|
||||||
- Below the breakpoint, the nav remains usable (medium links reachable via the hamburger).
|
removal, not retirement).
|
||||||
|
- Below the breakpoint, the nav remains usable: the medium links live in the hamburger **sub-list under
|
||||||
|
ARCHIVE** (8.H confirms mobile ARCHIVE → the searchable browser, with the medium links indented).
|
||||||
|
|
||||||
**Dependencies.** Depends on **8.H** (ARCHIVE's target). Coordinates with **8.J** (popover fate).
|
**Dependencies.** Depends on **8.H** (ARCHIVE's target). Coordinates with **8.J** (popover fate).
|
||||||
|
|
||||||
**Open question (flag).** Eliminate GENRES from the **nav only** (keep `/genres` route reachable), or
|
|
||||||
retire `GenresView` entirely? *Recommend nav-only removal* (consistent with the CMS genre-browse
|
|
||||||
disposition; the route is built and harmless). Confirm with Daniel.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### 8.J — ARCHIVE popover click does not close (bug)
|
### 8.J — ARCHIVE popover click does not close (bug)
|
||||||
@@ -437,22 +551,25 @@ exists). Can be specced and fixed independently; sequence after 8.I if 8.I resha
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 4. Mix Visualizer — 8.K `[design pending interview]`
|
## 4. Mix Visualizer — 8.K `[post-Phase-9, design-complete]`
|
||||||
|
|
||||||
**Do not write an implementation spec for this track.** Daniel has explicitly asked to be interviewed
|
**Out of Phase-9-completion scope (Daniel, 2026-06-13).** "Visualizer is outside the scope of phase 9
|
||||||
before the Mix Visualizer is redesigned. His seed idea: the waveform **scrolls** bottom-to-top in high
|
but we must document it now in complete detail." Phase 9 can close **without** 8.K. The interview has
|
||||||
resolution, with a slider controlling scroll speed / zoom level (higher resolution moves faster) —
|
run, and `product-notes/phase-9-mix-visualizer-redesign.md` is now a **finished, implementation-ready
|
||||||
**not** a static background image (which is what `MixWaveformVisualizer` renders today: a single static
|
design spec** — no longer a question set. A future wave can be dispatched straight from it.
|
||||||
full-viewport mirrored silhouette).
|
|
||||||
|
|
||||||
The structured interview question set is in `product-notes/phase-9-mix-visualizer-redesign.md`. It is
|
Headline of the captured design: the visualizer becomes a **windowed, playback-coupled, bottom-to-top
|
||||||
grounded in the current implementation (read 2026-06-13): an SVG silhouette built from a stored loudness
|
scrolling waveform** — a musical-score-going-by treatment showing only the currently-playing region.
|
||||||
profile, full-page background, with an inert click-to-seek seam already present. The questions probe the
|
Zoom couples to apparent scroll speed (Guitar-Hero model: zoomed in = shorter time-span fills the
|
||||||
motion model, zoom/resolution coupling, aesthetics, interaction, and performance so the eventual design
|
screen = faster apparent motion), with a hard anchor that **at maximum zoom exactly one quarter note is
|
||||||
is built on Daniel's actual intent, not a guess.
|
visible at 180 BPM (~333 ms of audio)**. It is a **lava-lamp, not test-equipment** aesthetic —
|
||||||
|
theme-aware gradients, glassy, **strictly read-only** (no seeking), a background/theming element.
|
||||||
|
Rendering shifts to standard Canvas/WebGL with **no tricks — industry-standard patterns, well
|
||||||
|
commented**. Full motion/zoom/aesthetic/interaction/performance/data spec, plus the datum-resolution
|
||||||
|
analysis and recommendation, in the design doc.
|
||||||
|
|
||||||
**This track stays `[design pending interview]` until the interview runs and a design is captured.** It
|
**This is the one Wave 8 track that does not gate Phase 9 completion.** It is design-complete and
|
||||||
must not be dispatched for implementation from this document.
|
sequenced as a post-Phase-9 implementation wave.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -463,37 +580,48 @@ must not be dispatched for implementation from this document.
|
|||||||
- **8.A** (tab strip) — consumes 8.B; the structural spine. Land after 8.B.
|
- **8.A** (tab strip) — consumes 8.B; the structural spine. Land after 8.B.
|
||||||
- **8.C** (per-medium edit), **8.E** (medium-aware Add Track) — layer onto 8.A; parallel with each
|
- **8.C** (per-medium edit), **8.E** (medium-aware Add Track) — layer onto 8.A; parallel with each
|
||||||
other once 8.A lands.
|
other once 8.A lands.
|
||||||
- **8.F** (Session hero in form), **8.G** (label rename) — **independent** of the tab work; land in
|
- **8.F** (Session hero in form), **8.G** (label rename), **8.L** (single-track name consolidation) —
|
||||||
parallel any time. 8.F pairs with 8.E (Session Add-Track → hero-capable form).
|
**independent** of the tab work; touch the upload/edit forms. Sequence **8.G → 8.L** (the consolidated
|
||||||
|
field should read "Release Name" first), and **coordinate 8.E/8.F/8.L** — all three touch the Session
|
||||||
|
upload form and its submit handler.
|
||||||
|
|
||||||
**Public cluster:**
|
**Public cluster:**
|
||||||
- **8.J** (popover dismissal bug) — **independent**; can land immediately (but coordinate with 8.I if
|
- **8.J** (popover dismissal bug) — **independent**; can land immediately (but coordinate with 8.I if
|
||||||
8.I reshapes the popover).
|
8.I reshapes the popover).
|
||||||
- **8.H** (archive = searchable browser) — **gated on Daniel's cardinality decision** (H1 vs H2).
|
- **8.H** (archive = release-cardinal searchable browser) — **decided (H2)**; build the new browser.
|
||||||
- **8.I** (nav slim + GENRES out) — depends on 8.H (ARCHIVE target); coordinates with 8.J.
|
- **8.I** (nav slim + GENRES out + `/tracks` demoted) — depends on 8.H (ARCHIVE target); coordinates
|
||||||
|
with 8.J.
|
||||||
|
|
||||||
**Mix Visualizer:**
|
**Mix Visualizer:**
|
||||||
- **8.K** — **blocked on the interview.** Not implementable from this doc.
|
- **8.K** — **out of Phase-9 scope; design-complete.** Sequenced as a post-Phase-9 implementation wave,
|
||||||
|
dispatchable straight from `phase-9-mix-visualizer-redesign.md`. Does not gate Phase 9 completion.
|
||||||
|
|
||||||
**Recommended sequencing.** Land the independent/trivial items first (8.G, 8.D, 8.J, 8.F) — they unblock
|
**Recommended sequencing.** Land the independent/trivial items first (8.G, 8.D, 8.J), then 8.L
|
||||||
nothing and need nothing. Then the CMS spine (8.B → 8.A → 8.C/8.E). On the public side, get Daniel's H1/
|
(consolidation, after 8.G). Then the CMS spine (8.B → 8.A → 8.C/8.E), folding 8.F into the Session-form
|
||||||
H2 decision, then 8.H → 8.I. Run the 8.K interview in parallel with all of it; it gates only itself.
|
work alongside 8.E/8.L. On the public side, 8.H (the new release-cardinal archive) → 8.I. **Phase 9
|
||||||
|
closes when 8.A–8.J + 8.L land; 8.K is explicitly excluded** and runs as a separate post-Phase-9 wave.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 6. Decisions needed from Daniel before / during build
|
## 6. Decisions — all resolved (Daniel, 2026-06-13)
|
||||||
|
|
||||||
1. **(8.H) Archive cardinality — H1 vs H2.** Is the public archive the existing **track** gallery
|
The open questions that gated this wave are answered. Recorded here as the decision log; baked into the
|
||||||
relabelled (H1, cheap), or a new **release**-cardinal searchable browser (H2, coherent with the CMS
|
acceptance criteria above.
|
||||||
archive and the medium views)? *This is the load-bearing product decision of the public cluster.*
|
|
||||||
Recommend H2 for model symmetry, H1 if speed dominates.
|
|
||||||
2. **(8.H) Mobile ARCHIVE destination.** With `/archive` becoming the searchable browser, does mobile
|
|
||||||
ARCHIVE go to the browser (recommended) or keep a card overview? Affects whether the three-card view
|
|
||||||
is fully retired.
|
|
||||||
3. **(8.I) GENRES — nav-only removal vs full retirement.** Recommend nav-only (keep `/genres`
|
|
||||||
reachable). Confirm.
|
|
||||||
4. **(8.F) Session hero image — optional vs required.** Daniel's wording ("allow") reads optional.
|
|
||||||
Recommend optional. Confirm.
|
|
||||||
5. **(8.E, minor) `ALL`-tab Add Track default medium.** Recommend Cut. Not blocking.
|
|
||||||
|
|
||||||
Items 1–4 are genuine product calls; 5 has a safe default and should not block.
|
1. **(8.H) Archive cardinality → H2.** The public archive is a **new release-cardinal searchable
|
||||||
|
browser** at `/archive`, not the relabelled track gallery. Cascade: `/tracks` (`TracksView`) is
|
||||||
|
demoted from the nav (route kept reachable); 8.I links ARCHIVE to the new browser.
|
||||||
|
2. **(8.H) Mobile ARCHIVE → the searchable browser.** The three-card overview is fully retired on every
|
||||||
|
breakpoint; medium links live in the hamburger sub-list under ARCHIVE.
|
||||||
|
3. **(8.I) GENRES → nav-only removal.** Drop the menu item; keep `/genres` + `GenresView` reachable. No
|
||||||
|
retirement.
|
||||||
|
4. **(8.F) Session hero → optional, but warn if missing.** No hard validation gate; the form surfaces a
|
||||||
|
warning when a Session is submitted without a hero image.
|
||||||
|
5. **(8.E) `ALL`-tab Add Track default medium → Cut.** The medium selector remains user-changeable after
|
||||||
|
landing on the upload form.
|
||||||
|
6. **(8.L, recommendation pending confirm) Single-track name sync.** Recommended posture: keep the
|
||||||
|
derived track name **synced** to the Release Name for Session/Mix on both create and edit, so they
|
||||||
|
never diverge. (Daniel set the consolidation requirement; the sync-vs-diverge interaction detail is
|
||||||
|
the one open recommendation — call it out, default to synced.)
|
||||||
|
7. **(8.K) Mix Visualizer → out of Phase-9 scope, design-complete.** Documented in full now; built
|
||||||
|
later. Phase 9 closes without it.
|
||||||
|
|||||||
Reference in New Issue
Block a user