docs: spec Phase 9 Wave 8 remediation + Mix Visualizer interview set
This commit is contained in:
@@ -165,6 +165,33 @@ Sequenced as four waves. Wave 1 is a prerequisite for everything; within Waves 2
|
|||||||
|
|
||||||
Waves 1–7 are landed (`COMPLETED.md §9`). Wave 6 closes two functional gaps a post-landing smoke-test survey surfaced — surfaces the medium taxonomy did *not* reach, not regressions. Wave 7 hardens the single-track-per-medium rule from a CMS-form convention into a real domain invariant — the one place the medium taxonomy is *declared but not enforced* below the UI.
|
Waves 1–7 are landed (`COMPLETED.md §9`). Wave 6 closes two functional gaps a post-landing smoke-test survey surfaced — surfaces the medium taxonomy did *not* reach, not regressions. Wave 7 hardens the single-track-per-medium rule from a CMS-form convention into a real domain invariant — the one place the medium taxonomy is *declared but not enforced* below the UI.
|
||||||
|
|
||||||
|
### 9.8 Wave 8 — Remediation (from Daniel's Phase-9 testing pass)
|
||||||
|
|
||||||
|
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`.
|
||||||
|
|
||||||
|
Full track decomposition, acceptance criteria, and parallel/dependent analysis: `product-notes/phase-9-wave-8-remediation.md`. The tracks in brief:
|
||||||
|
|
||||||
|
**CMS (`DeepDrftManager`):**
|
||||||
|
- **8.A — Release Archive as medium tabs, not cards.** Retire the three navigate-away medium cards (`ReleaseArchiveBrowser`); replace with an in-page tab strip (`ALL` + one tab per medium) that swaps the grid below in place. Retire the redundant top-level **Releases** toggle item (the `ALL` tab subsumes it). *(Depends on 8.B for the shared grid contract; 8.C/8.D/8.E layer onto it.)*
|
||||||
|
- **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.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.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.G — "Album Name" → "Release Name" label.** Rename the `AlbumHeaderFields` label. *(Independent, trivial.)*
|
||||||
|
|
||||||
|
**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.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.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:**
|
||||||
|
- **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`.
|
||||||
|
|
||||||
|
**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.
|
||||||
|
|
||||||
|
|
||||||
## Working with this file
|
## Working with this file
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,142 @@
|
|||||||
|
# Phase 9 — Wave 8.K: Mix Visualizer Redesign (Interview Question Set)
|
||||||
|
|
||||||
|
Status: **design pending interview**. Author: product-designer. Date: 2026-06-13.
|
||||||
|
**No implementation spec exists or should be written until the interview runs.**
|
||||||
|
|
||||||
|
Cross-references: `PLAN.md §9.8` (Wave 8 entry, 8.K), `product-notes/phase-9-wave-8-remediation.md §4`,
|
||||||
|
`product-notes/phase-9-release-medium-types.md §5.4` (the original `MixWaveformVisualizer` design).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Purpose
|
||||||
|
|
||||||
|
Daniel wants the Mix Visualizer **completely redesigned** and has **explicitly asked to be interviewed**
|
||||||
|
before any design is committed. This document is the structured question set the-boss relays to Daniel
|
||||||
|
to run that interview. It is **not** a spec. When the interview produces answers, they get captured here
|
||||||
|
(or in a successor design note), and only then does 8.K become implementable.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Current implementation (grounded, read 2026-06-13)
|
||||||
|
|
||||||
|
So the questions are anchored in what exists rather than asked blind:
|
||||||
|
|
||||||
|
- **Component:** `MixWaveformVisualizer.razor` + `.razor.cs` in `DeepDrftPublic.Client/Controls/`.
|
||||||
|
- **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)`,
|
||||||
|
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
|
||||||
|
single still shape; it does not move.
|
||||||
|
- **Layout:** rendered as the full-page background behind the Mix detail content
|
||||||
|
(`MixDetail.razor` places `<MixWaveformVisualizer>` behind a `.mix-detail-foreground` stacking layer).
|
||||||
|
- **Played-portion wash:** a `<rect>` clipped to the silhouette, width = `PlaybackPosition * width`,
|
||||||
|
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
|
||||||
|
is **not wired** — the seam was added for a future wave.
|
||||||
|
- **Data resolution:** the profile is the **high-resolution** Mix waveform datum computed server-side
|
||||||
|
(§9.2.B trigger) and stored in the vault; distinct from the player-bar low-res peek.
|
||||||
|
- **Explicit design boundary (from §5.4):** this component is deliberately **NOT** the player-bar
|
||||||
|
peak-bar idiom (`SpectrumVisualizer` / `LevelMeterFab`). Those own the player bar; the Mix visualizer
|
||||||
|
has its own visual language.
|
||||||
|
|
||||||
|
**Daniel's seed idea for the redesign:** NOT a static background image. Instead the waveform **scrolls
|
||||||
|
from the bottom of the screen to the top** in **high resolution**, with a **slider controlling scroll
|
||||||
|
speed / zoom level** — higher resolution moves faster. That is the entire brief so far; the interview
|
||||||
|
fills in the rest.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Interview questions
|
||||||
|
|
||||||
|
Grouped by theme. Relay to Daniel; capture answers inline or in a successor note.
|
||||||
|
|
||||||
|
### A. Motion & scroll behaviour
|
||||||
|
|
||||||
|
1. The waveform scrolls **bottom-to-top**. Is it the *whole mix's* waveform scrolling past (like a
|
||||||
|
scrolling score / piano-roll), or a *windowed* segment around the playback head? I.e. does the
|
||||||
|
waveform represent the entire track laid out vertically and scroll through it, or a moving window?
|
||||||
|
2. Is scroll **coupled to playback** (the visualizer scrolls because the track is playing, position =
|
||||||
|
playhead), or is it a **free ambient motion** independent of playback (scrolls even when paused /
|
||||||
|
nothing is playing)? Or both modes?
|
||||||
|
3. If coupled to playback: does the **current playback position** sit at a fixed point on screen (e.g.
|
||||||
|
always centre, or always at the top "now" line) with the waveform flowing past it? Where is "now"?
|
||||||
|
4. What happens at the **start and end** of the mix? Does it scroll in from empty / scroll out to empty,
|
||||||
|
loop, or hold?
|
||||||
|
5. Direction is bottom-to-top — is that fixed, or is direction itself something to play with (some
|
||||||
|
visualizers run top-down)? Confirm bottom-to-top is the intent.
|
||||||
|
|
||||||
|
### 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
|
||||||
|
|
||||||
|
Capture Daniel's answers (here or in a successor design note), then this track converts from
|
||||||
|
`[design pending interview]` to a real implementation spec — at which point the rendering-tech decision
|
||||||
|
(question 20), the data-resolution question (10, 24), and the interaction model (16–18) are the three
|
||||||
|
things most likely to drive the build's shape and should be settled first.
|
||||||
@@ -0,0 +1,499 @@
|
|||||||
|
# Phase 9 — Wave 8: Remediation
|
||||||
|
|
||||||
|
Status: spec (CMS + public tracks) / **design-pending-interview** (Mix Visualizer, 8.K).
|
||||||
|
Author: product-designer. Date: 2026-06-13.
|
||||||
|
**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`
|
||||||
|
(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 8.K interview question set), memory *One source, multiple views*.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 0. What this wave is, and what it is not
|
||||||
|
|
||||||
|
Daniel tested the landed Phase 9 surface (Waves 1–7, all on `dev`) and produced a punch-list. This
|
||||||
|
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.
|
||||||
|
|
||||||
|
Three clusters:
|
||||||
|
|
||||||
|
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
|
||||||
|
working edit + add affordances in every mode. The current `ReleaseArchiveBrowser` (three cards that
|
||||||
|
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
|
||||||
|
the two-step), and rename "Album Name" → "Release Name."
|
||||||
|
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.
|
||||||
|
|
||||||
|
**Not in this wave:** the **Mix Visualizer redesign (8.K)**. Daniel has explicitly asked to be
|
||||||
|
interviewed before it is designed. It is carried as `[design pending interview]` with a structured
|
||||||
|
question set in `phase-9-mix-visualizer-redesign.md`. No implementation spec exists or should be
|
||||||
|
authored until the interview runs.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Verified current state (read against live source 2026-06-13)
|
||||||
|
|
||||||
|
**CMS:**
|
||||||
|
- `TrackList.razor` (`/tracks`) has a `MudToggleGroup` with three items: **Tracks**, **Releases**
|
||||||
|
(`BrowseMode.Albums`), **Release Archive** (`BrowseMode.Archive`). Routes `/tracks`,
|
||||||
|
`/tracks/albums`, `/tracks/archive`, `/tracks/genres` all resolve here; Genres has no toggle item
|
||||||
|
but is reachable by URL.
|
||||||
|
- `BrowseMode.Archive` renders `ReleaseArchiveBrowser.razor` — a `MudGrid` of **three navigate-away
|
||||||
|
cards** (Cut → `/tracks/albums`, Session → `/tracks/sessions`, Mix → `/tracks/mixes`), driven off
|
||||||
|
`Enum.GetValues<ReleaseMedium>()` + a `MediumCards` lookup. The cards leave the page entirely.
|
||||||
|
- `BrowseMode.Albums` renders `CmsAlbumBrowser` — the cross-medium releases grid with a **Type** column
|
||||||
|
rendering `<MudChip>@context.Release.ReleaseType</MudChip>` unconditionally, plus a working
|
||||||
|
batch-edit button (`/tracks/album/{title}/edit`). This grid shows **all** releases regardless of
|
||||||
|
medium (CUTS, SESSIONS, MIXES together), and the Type chip always shows the Cut-only `ReleaseType`
|
||||||
|
(Single/EP/Album) — wrong for Session/Mix rows.
|
||||||
|
- `CmsSessionBrowser` (`/tracks/sessions`) and `CmsMixBrowser` (`/tracks/mixes`) are standalone routable
|
||||||
|
pages inheriting `CmsMediumBrowserBase`, each with a "Back to Release Archive" button and a per-row
|
||||||
|
Edit button (added in §9.5.E). They are **not** embedded in `TrackList`; they are navigated to.
|
||||||
|
- `CmsTrackGrid` (the `Tracks` mode) has an **Add Track** button (`Href="/tracks/upload"`) gated on
|
||||||
|
`ShowAddButton`. The album/archive modes have no add button.
|
||||||
|
- `SessionFields.razor` (shown in the upload form for `Medium == Session`) is **only a `MudAlert`**:
|
||||||
|
"After upload, set the hero image from the **Release Archive → Sessions** browser." No hero upload
|
||||||
|
input — the hero is set afterward, per-row, in `CmsSessionBrowser`. This is the two-step Daniel wants
|
||||||
|
collapsed.
|
||||||
|
- `AlbumHeaderFields.razor` labels the first field **"Album Name"** (`Label="Album Name"`,
|
||||||
|
`RequiredError="Album Name is required"`).
|
||||||
|
- Hero-image endpoint is **resource-addressed**: `POST api/release/{id}/session/hero-image` — it needs
|
||||||
|
a release id, which does not exist until the release is created. This is *why* `SessionFields` punts
|
||||||
|
to a post-upload step. Composing it into the form means deferring the hero upload to *after* the
|
||||||
|
create call returns an id, within the same submit handler (see 8.F note).
|
||||||
|
|
||||||
|
**Public site:**
|
||||||
|
- `ArchiveView.razor` (`/archive`) is the **three-card overview** (Cuts / Sessions / Mixes), each an
|
||||||
|
`<a href>` to the medium view.
|
||||||
|
- `TracksView.razor` (`/tracks`) is the **flat track gallery** — search field, album/genre filter
|
||||||
|
pills, grid/list of *tracks* (track-cardinal). Title: "DeepDrft Track Gallery."
|
||||||
|
- `AlbumsView.razor` (`/cuts`) is the **release-cardinal** Cuts gallery (album cards).
|
||||||
|
- `Pages.cs` `MenuPages`: **ARCHIVE** (route `/archive`, children Cuts/Sessions/Mixes) + **Tracks**
|
||||||
|
(`/tracks`) + **Genres** (`/genres`).
|
||||||
|
- `DeepDrftMenu.razor` desktop renders ARCHIVE as a dual-role node: parent `<a href="/archive">` plus a
|
||||||
|
**pure-CSS hover dropdown** (`.dd-nav-dropdown`, shown via `:hover`/`:focus-within` in
|
||||||
|
`DeepDrftMenu.razor.css`). There is **no JS dismissal** — the dropdown hides only when the cursor
|
||||||
|
leaves the parent region or focus moves out. On SPA navigation (Blazor enhanced nav keeps the DOM),
|
||||||
|
the cursor often remains over the parent, so the dropdown stays visible: the "stuck open" bug (8.J).
|
||||||
|
- All three medium views (`/cuts`, `/sessions`, `/mixes`) and the `ReleaseClient`/`ReleaseProxyController`
|
||||||
|
read family exist and work.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. CMS tracks — 8.A through 8.G
|
||||||
|
|
||||||
|
### 8.A — Release Archive as medium tabs, not navigate-away cards
|
||||||
|
|
||||||
|
**Goal.** Replace the Release Archive card grid with an **in-page tab strip** of medium modes that swap
|
||||||
|
the grid below in place, instead of navigating to separate pages. Add an `ALL` tab to the left of the
|
||||||
|
medium tabs. Retire the redundant top-level **Releases** toggle item — the `ALL` tab subsumes it.
|
||||||
|
|
||||||
|
**User-visible change.** Opening the Release Archive (or the CMS tracks page in its archive role) shows
|
||||||
|
a tab strip: **`ALL` · CUTS · SESSIONS · MIXES**. Selecting a tab swaps the grid below without leaving
|
||||||
|
the page. The old three-card "click to go to another page" interaction is gone, as is the separate
|
||||||
|
**Releases** toggle button (it is now the `ALL` tab).
|
||||||
|
|
||||||
|
**Shape (informing, not prescribing — staff-engineer owns implementation).**
|
||||||
|
- The cleanest structure folds the medium dimension into `TrackList`'s existing mode model. Today the
|
||||||
|
top-level toggle is `Tracks / Releases / Release Archive`. Daniel's ask collapses *Releases* and
|
||||||
|
*Release Archive* into one **release** view with an inner medium tab strip (`ALL / Cut / Session /
|
||||||
|
Mix`). Whether the medium tabs live as a second toggle group inside the Archive mode, or the whole
|
||||||
|
top-level toggle is restructured, is an implementation call — the **user-visible** requirement is the
|
||||||
|
four-tab strip swapping the grid in place.
|
||||||
|
- `ReleaseArchiveBrowser` (the card grid) is **retired** as the archive landing. The medium→display
|
||||||
|
lookup it holds (label/descriptor) may survive as tab labels.
|
||||||
|
- The standalone `/tracks/sessions` and `/tracks/mixes` routes: keep them reachable (a hard 404 on a
|
||||||
|
previously-working URL is hostile), but they are no longer the *primary* path — the tabs are. The
|
||||||
|
"Back to Release Archive" buttons in `CmsSessionBrowser`/`CmsMixBrowser` lose their meaning if those
|
||||||
|
browsers become embedded tab content; resolve their fate as part of 8.C.
|
||||||
|
|
||||||
|
**Acceptance criteria.**
|
||||||
|
- The Release Archive surface shows a tab strip with `ALL`, plus one tab per `ReleaseMedium`, `ALL`
|
||||||
|
left-most. (Tabs driven off the enum + a label lookup, not a hardcoded three-arm switch — preserve
|
||||||
|
the Phase 9 extension discipline so a fourth medium surfaces a tab automatically.)
|
||||||
|
- Selecting a tab swaps the grid below in place; no navigation to a separate page occurs.
|
||||||
|
- The top-level **Releases** toggle item is removed; its grid is reachable via the `ALL` tab.
|
||||||
|
- A fourth medium added to the enum surfaces a new tab with no markup change beyond one lookup entry.
|
||||||
|
|
||||||
|
**Dependencies.** Consumes the shared grid contract from **8.B** (the `ALL` grid) and the per-medium
|
||||||
|
grids from **8.C**. Land 8.B first (it defines the grid each tab renders), then 8.A wires the tab strip,
|
||||||
|
then 8.C/8.E layer the per-tab affordances. 8.A is the structural spine of the CMS cluster.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 8.B — `ALL` tab: all-releases grid with working edit
|
||||||
|
|
||||||
|
**Goal.** The left-most `ALL` tab shows the current cross-medium releases grid (every release,
|
||||||
|
all media) with working edit buttons — the surface the retired **Releases** toggle showed.
|
||||||
|
|
||||||
|
**User-visible change.** The `ALL` tab presents the full releases list (CUTS, SESSIONS, MIXES together)
|
||||||
|
exactly as the current Releases grid does today, with an edit affordance per row.
|
||||||
|
|
||||||
|
**Shape.** This is essentially the current `CmsAlbumBrowser` grid (which already lists all releases and
|
||||||
|
already has a working batch-edit button) re-homed as the `ALL` tab's content. The main *new* work is
|
||||||
|
making it the default tab and ensuring the Type column behaves (that correctness fix is **8.D**, which
|
||||||
|
this tab consumes). No new data path — `CmsAlbumBrowser` already loads the cross-medium release list.
|
||||||
|
|
||||||
|
**Acceptance criteria.**
|
||||||
|
- The `ALL` tab lists every release regardless of medium.
|
||||||
|
- Each row has a working Edit button routing to the release's edit page.
|
||||||
|
- The grid matches the behaviour of today's Releases toggle grid (no regression in sort, delete,
|
||||||
|
expand-tracks).
|
||||||
|
|
||||||
|
**Dependencies.** Foundation for 8.A (the tab renders this grid). Independent of 8.C/8.D/8.E for build,
|
||||||
|
though 8.D's Type-chip fix lands *in* this grid. Recommend: 8.B + 8.D land together (same grid), then
|
||||||
|
8.A wires tabs.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 8.C — Per-medium tab grids gain working edit affordances
|
||||||
|
|
||||||
|
**Goal.** The Cut / Session / Mix tab grids each get an Edit action that routes intuitively to the
|
||||||
|
correct edit page for that medium.
|
||||||
|
|
||||||
|
**User-visible change.** In each medium tab, every row has an Edit button. Clicking it opens the edit
|
||||||
|
form appropriate to that medium (Cut → batch/release edit with `ReleaseType`; Session/Mix → the
|
||||||
|
single-track edit with the medium-appropriate fields, no `ReleaseType`).
|
||||||
|
|
||||||
|
**Shape.** `CmsSessionBrowser` and `CmsMixBrowser` already have per-row Edit buttons (added §9.5.E)
|
||||||
|
routing to `/tracks/album/{title}/edit` (`BatchEdit`). The Cut tab reuses `CmsAlbumBrowser`'s existing
|
||||||
|
edit button. The work here is (a) ensuring each tab's grid carries the edit action when embedded as tab
|
||||||
|
content rather than a standalone page, and (b) confirming the edit destination is the right one per
|
||||||
|
medium — `BatchEdit` already collapses to single-track for Session/Mix (§9.6.B), so the same route
|
||||||
|
works for all three; verify it presents correctly.
|
||||||
|
|
||||||
|
**Acceptance criteria.**
|
||||||
|
- Every row in the Cut, Session, and Mix tab grids has a working Edit button.
|
||||||
|
- Cut edit opens with `ReleaseType` visible; Session/Mix edit opens single-track with no `ReleaseType`
|
||||||
|
(consistent with the landed §9.6.B collapse).
|
||||||
|
- Edit from any medium tab loads the correct release and returns to a sensible place after save.
|
||||||
|
|
||||||
|
**Dependencies.** Depends on **8.A** (the tabs must exist to host the grids). Parallel with **8.D** and
|
||||||
|
**8.E** once 8.A lands.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 8.D — Type column chip reads "Session" / "DJ Mix" for non-Cuts
|
||||||
|
|
||||||
|
**Goal.** The cross-medium grid's **Type** column must not show a Cut-only `ReleaseType` (Single/EP/
|
||||||
|
Album) chip for Session/Mix rows. For non-Cut media the chip reads the medium name — **"Session"** or
|
||||||
|
**"DJ Mix"**.
|
||||||
|
|
||||||
|
**User-visible change.** In the `ALL` grid (and anywhere the cross-medium Type column appears), a Cut
|
||||||
|
row shows its release type (Single/EP/Album) as today; a Session row shows **"Session"**; a Mix row
|
||||||
|
shows **"DJ Mix"**.
|
||||||
|
|
||||||
|
**Shape.** The Type cell currently renders `@context.Release.ReleaseType` unconditionally. Per the
|
||||||
|
Phase 9 read-model design, `ReleaseDto.ReleaseType` is **nullable** and nulled for non-Cut media at the
|
||||||
|
mapping point — so for Session/Mix the chip is rendering a default/empty or stale value. The cell
|
||||||
|
becomes medium-aware: when `Medium == Cut`, show `ReleaseType`; otherwise show the medium's display
|
||||||
|
name ("Session", "DJ Mix"). Drive the display-name from a medium→label lookup (the same extension
|
||||||
|
discipline — not an inline `if session ... else if mix`), so a future medium's label comes free. Note
|
||||||
|
Daniel's exact wording: **"DJ Mix"** for the Mix medium, not "Mix."
|
||||||
|
|
||||||
|
**Acceptance criteria.**
|
||||||
|
- A Cut row's Type chip shows Single/EP/Album as today.
|
||||||
|
- A Session row's Type chip shows "Session"; a Mix row's shows "DJ Mix".
|
||||||
|
- No row shows a Cut-only `ReleaseType` value for a non-Cut medium.
|
||||||
|
|
||||||
|
**Dependencies.** Independent — a self-contained cell-rendering fix. Lands naturally with 8.B (same
|
||||||
|
grid). Can be built and tested before the tab restructure.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 8.E — Add-Track buttons in all modes, medium-aware routing
|
||||||
|
|
||||||
|
**Goal.** An **Add Track** button appears in every CMS mode/tab, routing to the correct upload page
|
||||||
|
**pre-set to the selected tab's medium**.
|
||||||
|
|
||||||
|
**User-visible change.** Whatever tab the admin is on (`ALL`, Cut, Session, Mix), an Add Track button is
|
||||||
|
present. Clicking it opens the upload page with the medium already set to that tab (a Session tab's Add
|
||||||
|
Track opens the upload form in Session mode; a Mix tab's opens it in Mix mode). On the `ALL` tab, Add
|
||||||
|
Track opens the upload page at its default (Cut) — or whatever Daniel prefers as the neutral default
|
||||||
|
(flag below).
|
||||||
|
|
||||||
|
**Shape.** `CmsTrackGrid` already has an Add Track button (`Href="/tracks/upload"`). The work is (a)
|
||||||
|
surfacing it in the release/archive modes too, and (b) carrying the medium to the upload page. The
|
||||||
|
upload page (`TrackNew`/`BatchUpload`) already has a `MediumFields` selector defaulting to Cut; the
|
||||||
|
cleanest route is a query param (e.g. `/tracks/upload?medium=session`) that pre-selects the medium on
|
||||||
|
load. This mirrors how `/cuts`-style routes carry a medium filter — a single query-param convention,
|
||||||
|
not a per-medium route fork.
|
||||||
|
|
||||||
|
**Acceptance criteria.**
|
||||||
|
- 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.
|
||||||
|
- The pre-selected medium drives the conditional form fields immediately (Session shows the hero field
|
||||||
|
per 8.F; Cut shows `ReleaseType`; etc.).
|
||||||
|
|
||||||
|
**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).
|
||||||
|
|
||||||
|
**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)
|
||||||
|
|
||||||
|
**Goal.** Author a Session — including its hero image — in a **single upload pass**. Remove the
|
||||||
|
"set the hero image later from the Release Archive → Sessions browser" message; compose the
|
||||||
|
polymorphic metadata fields so the Session form carries its hero-image input.
|
||||||
|
|
||||||
|
**User-visible change.** Uploading a Session no longer shows the "do it in two steps" alert. The Session
|
||||||
|
upload form has a hero-image file input alongside cover art; on submit, both the release and its hero
|
||||||
|
image are created in one flow. The post-upload per-row hero step in `CmsSessionBrowser` becomes a
|
||||||
|
*correction* path (replace/fix), not the *required* authoring path.
|
||||||
|
|
||||||
|
**Shape (the ordering subtlety is the crux).** The hero endpoint is resource-addressed —
|
||||||
|
`POST api/release/{id}/session/hero-image` needs a release id that does not exist until the release is
|
||||||
|
created. This is precisely why `SessionFields` punts today. Composing it into the form does **not**
|
||||||
|
require a new endpoint; it requires the submit handler to sequence:
|
||||||
|
1. create the release via the existing upload path (returns the release id),
|
||||||
|
2. *then* POST the held hero-image file to `…/{id}/session/hero-image`,
|
||||||
|
all within one user gesture. The hero file is selected in the form, held client-side, and uploaded
|
||||||
|
after the create returns. This is the same deferred-upload pattern `AlbumHeaderFields` already uses for
|
||||||
|
cover art ("Will upload on submit"). The hero input belongs in `SessionFields` (replacing the alert)
|
||||||
|
or in the `MediumFields` dispatch — staff-engineer's call — but the *user* sees one form, one submit.
|
||||||
|
|
||||||
|
`SessionFields.razor`'s current body (a `MudAlert` only) is replaced by a real hero-image input.
|
||||||
|
|
||||||
|
**Acceptance criteria.**
|
||||||
|
- 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
|
||||||
|
flow — no separate manual step required.
|
||||||
|
- The "set hero from the browser later" alert is removed.
|
||||||
|
- 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
|
||||||
|
the browser (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
|
||||||
|
sequencing. Pairs naturally with **8.E** (Session Add-Track should land on this improved form). Can be
|
||||||
|
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
|
||||||
|
|
||||||
|
**Goal.** The `AlbumHeaderFields` first-field label reads **"Release Name"**, not "Album Name."
|
||||||
|
|
||||||
|
**User-visible change.** The upload/edit form's first field is labelled "Release Name" (and its
|
||||||
|
required-error message matches). Since the field now covers Cuts, Sessions, and Mixes — not just albums
|
||||||
|
— "Release Name" is the accurate noun.
|
||||||
|
|
||||||
|
**Shape.** Rename `Label="Album Name"` → `Label="Release Name"` and the `RequiredError` string in
|
||||||
|
`AlbumHeaderFields.razor`. Trivial. (Check whether any other surface labels the same field "Album" and
|
||||||
|
should follow for consistency — e.g. placeholder/help text — but the named change is the label.)
|
||||||
|
|
||||||
|
**Acceptance criteria.**
|
||||||
|
- The first field of the release header form reads "Release Name."
|
||||||
|
- The required-validation message references "Release Name."
|
||||||
|
|
||||||
|
**Dependencies.** Fully independent. Trivial. Can land any time.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Public site — 8.H through 8.J
|
||||||
|
|
||||||
|
### 8.H — Archive page becomes the searchable all-releases browser
|
||||||
|
|
||||||
|
**Goal.** Retarget `/archive` from the dead three-card overview to **the searchable view of all
|
||||||
|
releases**. The searchable all-releases browser *is* the archive.
|
||||||
|
|
||||||
|
**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
|
||||||
|
surface.
|
||||||
|
|
||||||
|
**Shape — and the framing tension that needs a decision.** Daniel's note says the Archive should be
|
||||||
|
"what the TRACKS page links to now — the searchable view of all releases," and that "naming everything
|
||||||
|
TRACKS is misleading." There is a real cardinality mismatch to resolve:
|
||||||
|
|
||||||
|
- The current `/tracks` (`TracksView`) is **track-cardinal** — it lists individual *tracks* with search
|
||||||
|
and album/genre filter pills. It is the searchable view that exists today.
|
||||||
|
- The medium browsers (`/cuts`, `/sessions`, `/mixes`) are **release-cardinal**.
|
||||||
|
- Daniel says "all releases" — which reads release-cardinal — but points at `TracksView`, which is
|
||||||
|
track-cardinal.
|
||||||
|
|
||||||
|
Two readings, and Daniel should pick:
|
||||||
|
|
||||||
|
- **(H1) Archive = the existing track gallery, renamed.** Move/retarget `TracksView`'s searchable
|
||||||
|
gallery to `/archive`, drop the misleading "Tracks" naming, and treat the flat searchable list as the
|
||||||
|
archive. Lowest effort — it is the view that exists, relabelled and re-homed. *But* it is
|
||||||
|
track-cardinal, which sits oddly with "all releases" and with the release-cardinal medium views it
|
||||||
|
sits beside.
|
||||||
|
- **(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
|
||||||
|
CMS side (8.A) is moving to a release-cardinal, medium-filtered archive — symmetry argues for (H2) on
|
||||||
|
the public side too (*One source, multiple views*: the same browse model, CMS and public). But (H1) is
|
||||||
|
materially cheaper and may be all Daniel wants. **This is a product decision — flag for Daniel before
|
||||||
|
building 8.H.** Do not pick by default; the cardinality choice cascades into 8.I (what ARCHIVE links to)
|
||||||
|
and the fate of `/tracks`.
|
||||||
|
|
||||||
|
**Acceptance criteria (conditional on the framing decision).**
|
||||||
|
- `/archive` renders a searchable browse surface (search + filter), not the three-card overview.
|
||||||
|
- 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.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 8.I — Nav slimmed: ARCHIVE + three medium modes inline, GENRES removed
|
||||||
|
|
||||||
|
**Goal.** Above the medium breakpoint the appbar carries **ARCHIVE** (→ all-releases browser) and the
|
||||||
|
**three medium modes** (CUTS / SESSIONS / MIXES → their view pages) directly; **GENRES is eliminated**
|
||||||
|
from the nav.
|
||||||
|
|
||||||
|
**User-visible change.** The desktop nav shows ARCHIVE plus the three medium links laid out across the
|
||||||
|
appbar (Daniel: "The ARCHIVE popover items can fill the appbar above the medium breakpoint"). GENRES no
|
||||||
|
longer appears in the nav. ARCHIVE links to the all-releases browser (8.H); each medium link goes to its
|
||||||
|
view page.
|
||||||
|
|
||||||
|
**Shape.** `Pages.cs` `MenuPages` currently nests Cuts/Sessions/Mixes as `Children` of ARCHIVE (a hover
|
||||||
|
popover) and carries Tracks + Genres as siblings. Daniel's ask flattens this above the breakpoint: the
|
||||||
|
three medium items become top-level appbar links (not hidden in a popover), ARCHIVE is its own link to
|
||||||
|
the browser, and Genres is dropped. Below the breakpoint (mobile) the existing hamburger indented-child
|
||||||
|
pattern can keep the medium links under ARCHIVE.
|
||||||
|
|
||||||
|
Note this **changes the popover model**: if the three media are inline above the breakpoint, the desktop
|
||||||
|
ARCHIVE popover may no longer be needed there at all — which also dissolves the 8.J stuck-open bug at
|
||||||
|
the desktop breakpoint (though 8.J should still be fixed for any breakpoint where a popover survives,
|
||||||
|
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.
|
||||||
|
|
||||||
|
`/genres` route and `GenresView` — Daniel says eliminate GENRES from the *nav*. Recommend the same
|
||||||
|
posture Phase 9 took with CMS genre browse: **drop the nav item, keep the route reachable** (no active
|
||||||
|
development, no hard removal) unless Daniel says retire it wholesale. Flag.
|
||||||
|
|
||||||
|
**Acceptance criteria.**
|
||||||
|
- Above the medium breakpoint, ARCHIVE and CUTS / SESSIONS / MIXES appear as appbar links.
|
||||||
|
- ARCHIVE links to the all-releases browser (8.H's output).
|
||||||
|
- Each medium link navigates to its view page (`/cuts`, `/sessions`, `/mixes`).
|
||||||
|
- GENRES no longer appears in the nav.
|
||||||
|
- Below the breakpoint, the nav remains usable (medium links reachable via the hamburger).
|
||||||
|
|
||||||
|
**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)
|
||||||
|
|
||||||
|
**Goal.** Clicking an item in the ARCHIVE popover **closes the popover**. Today it stays stuck open.
|
||||||
|
|
||||||
|
**User-visible change.** Clicking a popover child (Cuts/Sessions/Mixes) navigates *and* the dropdown
|
||||||
|
dismisses, instead of remaining visible over the destination page.
|
||||||
|
|
||||||
|
**Shape (root cause, verified).** The desktop dropdown is **pure CSS** — `.dd-nav-dropdown` is shown by
|
||||||
|
`.dd-nav-item-parent:hover` / `:focus-within` in `DeepDrftMenu.razor.css`, with no JS dismissal.
|
||||||
|
Clicking a child is an `<a href>` SPA navigation (Blazor enhanced nav preserves the DOM), so after
|
||||||
|
navigation the cursor is often still over the parent region — `:hover` remains true — and the dropdown
|
||||||
|
stays visible. A pure-CSS hover dropdown has no "I was clicked, now dismiss" state.
|
||||||
|
|
||||||
|
Fix direction (informing, not prescribing): give the dropdown a dismissal trigger on child click — e.g.
|
||||||
|
blur the active element / move focus, or add a small interactivity hook that collapses the dropdown on
|
||||||
|
navigation, or restructure so a click toggles a closable state. The mobile menu already closes on click
|
||||||
|
(`@onclick="CloseMobileMenu"`); the desktop popover needs an equivalent. **Note:** if 8.I removes the
|
||||||
|
desktop popover (medium links inline above the breakpoint), this bug may only remain on whatever
|
||||||
|
breakpoint still shows a popover — fix it there. Confirm the surviving popover surfaces with 8.I before
|
||||||
|
implementing.
|
||||||
|
|
||||||
|
**Acceptance criteria.**
|
||||||
|
- Clicking a popover child navigates to the target and the dropdown is no longer visible afterward.
|
||||||
|
- Hover-to-open still works (no regression to the open behaviour).
|
||||||
|
- Keyboard focus dismissal still behaves (no trap).
|
||||||
|
|
||||||
|
**Dependencies.** Independent bug fix, but **coordinate with 8.I** (which may change where the popover
|
||||||
|
exists). Can be specced and fixed independently; sequence after 8.I if 8.I reshapes the popover.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Mix Visualizer — 8.K `[design pending interview]`
|
||||||
|
|
||||||
|
**Do not write an implementation spec for this track.** Daniel has explicitly asked to be interviewed
|
||||||
|
before the Mix Visualizer is redesigned. His seed idea: the waveform **scrolls** bottom-to-top in high
|
||||||
|
resolution, with a slider controlling scroll speed / zoom level (higher resolution moves faster) —
|
||||||
|
**not** a static background image (which is what `MixWaveformVisualizer` renders today: a single static
|
||||||
|
full-viewport mirrored silhouette).
|
||||||
|
|
||||||
|
The structured interview question set is in `product-notes/phase-9-mix-visualizer-redesign.md`. It is
|
||||||
|
grounded in the current implementation (read 2026-06-13): an SVG silhouette built from a stored loudness
|
||||||
|
profile, full-page background, with an inert click-to-seek seam already present. The questions probe the
|
||||||
|
motion model, zoom/resolution coupling, aesthetics, interaction, and performance so the eventual design
|
||||||
|
is built on Daniel's actual intent, not a guess.
|
||||||
|
|
||||||
|
**This track stays `[design pending interview]` until the interview runs and a design is captured.** It
|
||||||
|
must not be dispatched for implementation from this document.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Dependency and parallelization summary
|
||||||
|
|
||||||
|
**CMS cluster:**
|
||||||
|
- **8.B** (all-releases grid) + **8.D** (Type chip fix) — land together (same grid), foundational.
|
||||||
|
- **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
|
||||||
|
other once 8.A lands.
|
||||||
|
- **8.F** (Session hero in form), **8.G** (label rename) — **independent** of the tab work; land in
|
||||||
|
parallel any time. 8.F pairs with 8.E (Session Add-Track → hero-capable form).
|
||||||
|
|
||||||
|
**Public cluster:**
|
||||||
|
- **8.J** (popover dismissal bug) — **independent**; can land immediately (but coordinate with 8.I if
|
||||||
|
8.I reshapes the popover).
|
||||||
|
- **8.H** (archive = searchable browser) — **gated on Daniel's cardinality decision** (H1 vs H2).
|
||||||
|
- **8.I** (nav slim + GENRES out) — depends on 8.H (ARCHIVE target); coordinates with 8.J.
|
||||||
|
|
||||||
|
**Mix Visualizer:**
|
||||||
|
- **8.K** — **blocked on the interview.** Not implementable from this doc.
|
||||||
|
|
||||||
|
**Recommended sequencing.** Land the independent/trivial items first (8.G, 8.D, 8.J, 8.F) — they unblock
|
||||||
|
nothing and need nothing. Then the CMS spine (8.B → 8.A → 8.C/8.E). On the public side, get Daniel's H1/
|
||||||
|
H2 decision, then 8.H → 8.I. Run the 8.K interview in parallel with all of it; it gates only itself.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. Decisions needed from Daniel before / during build
|
||||||
|
|
||||||
|
1. **(8.H) Archive cardinality — H1 vs H2.** Is the public archive the existing **track** gallery
|
||||||
|
relabelled (H1, cheap), or a new **release**-cardinal searchable browser (H2, coherent with the CMS
|
||||||
|
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.
|
||||||
Reference in New Issue
Block a user