docs: Phase 9 Waves 3+4 landed — move 9.3 and 9.4 from PLAN to COMPLETED

This commit is contained in:
daniel-c-harvey
2026-06-13 07:33:33 -04:00
parent 3ea4eb143b
commit 412c0334c6
2 changed files with 63 additions and 53 deletions
+63
View File
@@ -8,6 +8,69 @@ Newest entries at the top. Group by phase/wave header (mirroring `PLAN.md` / `CM
## Phase 9 — Release Medium Types
### 9.4 Wave 4 — Public site: ARCHIVE nav, CUTS / SESSIONS / MIXES, waveform visualizer
**Landed:** 2026-06-13 on dev.
- **9.4.A — ARCHIVE nav + popover.**
- **What:** Replace the current RELEASES / SESSIONS / MIXES nav items (in `DeepDrftPublic.Client/Layout/Pages.cs`) with a single **ARCHIVE** item. Desktop: hover shows a MudBlazor popover with CUTS / SESSIONS / MIXES → `/cuts`, `/sessions`, `/mixes`. Mobile / direct nav: ARCHIVE → an overview page `/archive` (three medium cards, reusing the §8.6 card idiom). Fixes the current **dead** Sessions/Mixes links.
- **Why:** The nav must route into the new medium surfaces; today's Sessions/Mixes links point nowhere.
- **Shape:** `DeepDrftMenu.razor` renders `Pages.MenuPages` as a flat `<a>` list today with no dropdown mechanism. Recommend extending the nav model with an optional `Children` collection (generalizes to future dropdowns) over a bespoke hardcoded popover. Pinned semantics (spec §5.1): dual-role nodes — desktop hover opens children, desktop click navigates to the parent's route (`/archive`), mobile renders the parent as a link with children indented; depth cap of **one level** — deeper nesting is a redesign, not a recursion.
- **Acceptance criteria:** ARCHIVE replaces the three flat items; desktop hover reveals the three sub-links; mobile routes to `/archive`; no dead links remain.
- **9.4.B — CUTS (`/cuts`).**
- **What:** New `/cuts` route reusing the existing `AlbumsView` layout, filtered to `Medium == Cut`. Studio Singles/EPs/Albums appear as they do on the current Releases page.
- **Why:** Honour the existing studio-release browse under the new medium taxonomy. Lowest-effort of the three media.
- **Shape:** Parameterize `AlbumsView`'s data load with a medium filter rather than forking a component. `/cuts` = `AlbumsView` with `Medium == Cut`.
- **Acceptance criteria:** `/cuts` shows only `Cut` releases with the current AlbumsView ergonomics.
- **Resolved:** When `/cuts` lands, the existing `/albums` route issues a redirect to `/cuts`. Old URLs keep working; no hard 404.
- **9.4.C — SESSIONS (`/sessions` + `/sessions/{id}`).**
- **What:** Gallery of session cards (cover, session name, artist) at `/sessions`; detail at `/sessions/{id}` mirroring `TrackDetail` but with the **hero image dominant above the fold**, cover secondary.
- **Why:** Sessions are an authored content kind the home page advertises; the hero image is their distinctive visual.
- **Shape:** Gallery borrows `AlbumsView`'s card-gallery skeleton with a session card face. Detail composes a shared `ReleaseDetailScaffold` (extracted common metadata + play + player wiring) with a hero-image hero slot — see 9.4.D open question.
- **Acceptance criteria:** `/sessions` lists Session releases; `/sessions/{id}` renders hero-dominant with the play affordance intact.
- **9.4.D — MIXES (`/mixes` + `/mixes/{id}`) + `MixWaveformVisualizer`.**
- **What:** Gallery at `/mixes`; detail at `/mixes/{id}` whose defining visual is a **`MixWaveformVisualizer`** component fed by the preprocessed waveform datum from `MixMetadata`, rendered as the **full-page background** of the detail page. The visualizer is a **named, reusable** component.
- **Why:** Mixes are long continuous sets; the waveform is their signature visual and the brief calls for a reusable visualizer.
- **Shape:** `MixWaveformVisualizer` takes the waveform datum (via `WaveformEntryKey` → content endpoint) + optional playback-position binding; renders a high-resolution, sophisticated **full-page background** visual in **its own visual language** — explicitly *not* the `SpectrumVisualizer` / `LevelMeterFab` peak-bar idiom, which is **reserved for the player bar**. The two are siblings in subject matter (waveforms) with entirely separate design treatments; they share a data pipeline (9.2.B), never a look. Detail composes the same `ReleaseDetailScaffold`, with the visualizer as the page-background layer.
- **Acceptance criteria:** `/mixes` lists Mix releases; `/mixes/{id}` renders the waveform visualizer as the page background fed by real datum (seedable via the 9.2.B trigger, no CMS required); the visualizer is a standalone reusable component visually distinct from the player-bar idiom.
- **Open question:** Design the visualizer's seek-on-click position-binding seam now even if click-to-seek ships later? Recommend yes — design the seam, defer the feature (*Design for adaptability up front*).
- **Prerequisite:** 9.2 (the `api/release` read family). Independent of Wave 3 for both **build and acceptance** — the body-less 9.2.B waveform trigger seeds real Mix datum and a script can seed hero images, with no CMS in existence.
- **Open questions:**
- **Detail-page strategy.** Three separate detail pages vs. one branching `TrackDetail` vs. a shared `ReleaseDetailScaffold` + per-medium hero slot. Recommend the scaffold (DRY-by-composition, the Phase 8 `BatchUpload`/`BatchEdit` extraction move; honours *One source, multiple views*). Sets the shape of 9.4.C and 9.4.D. Scaffold contract (spec §5.3): it owns exactly the invariant trio — metadata block, play affordance, player wiring; all per-medium variance rides slots (a boolean layout parameter on the scaffold is a design failure). `TrackDetail` is refactored onto the scaffold in this wave (it is the extraction source — nearly free); if deferred, record the fork as deliberate debt with a retirement note.
**Completion note:** ARCHIVE nav item implemented in `DeepDrftMenu.razor` with optional `Children` collection support in the page model for desktop popover/mobile dropdown. `/archive` overview page renders three medium cards (reusing §8.6 card design). `/cuts` route added, parameterizing `AlbumsView` with medium filter; `/albums` redirects to `/cuts`. `/sessions` gallery and `/sessions/{id}` detail pages implemented with hero-image-dominant layout; detail composes shared `ReleaseDetailScaffold`. `/mixes` gallery and `/mixes/{id}` detail pages implemented; detail features `MixWaveformVisualizer` full-page background component rendering waveform from `MixMetadata.WaveformEntryKey`. `ReleaseDetailScaffold` extracted from `TrackDetail` carrying invariant metadata + play + player wiring; `TrackDetail` refactored to use scaffold. `ReleaseClient` HTTP service and `ReleaseClientDataService` implemented alongside `ReleaseProxyController` in `DeepDrftPublic`. Waveform visualizer click-to-seek position binding seam designed (inert, feature shipping later). All acceptance criteria met; Wave 4 completes Phase 9 on the public site.
---
### 9.3 Wave 3 — CMS: Release Archive tab, medium selector, medium browsers
**Landed:** 2026-06-13 on dev.
- **9.3.A — Release Archive tab + medium selector.**
- **What:** Rename `TrackList.razor`'s third tab **Genre → Release Archive**. Inside it, render a **medium card group** (one card per `ReleaseMedium`, styled like the existing `CmsGenreBrowser` cards) where each card *navigates* to a medium-specific browser. Add a `ReleaseMedium` selector to `TrackNew` / `TrackEdit` / `BatchUpload` / `BatchEdit` / `AlbumHeaderFields`; show `ReleaseType` only when `Medium == Cut`, hide it (and surface medium-specific fields) for Session/Mix.
- **Why:** The CMS needs to author medium per release and browse the archive by medium. The card-group-of-media is the CMS analogue of the home page's three-medium block.
- **Shape:** Cards driven by `Enum.GetValues<ReleaseMedium>()` + a display-metadata lookup (label/descriptor/swatch) — **no hardcoded card switch**. Cut card → `CmsAlbumBrowser` (reused, with a `MediumFilter`); Session card → `CmsSessionBrowser`; Mix card → `CmsMixBrowser`. Selector-driven conditional fields ride **per-medium section components** (`CutFields` / `SessionFields` / `MixFields` — plain explicit markup inside, no clever generics) behind a **single dispatch point** (a `MediumFields` component holding the one `@switch`) embedded by all five forms — one dispatch, not five scattered conditional blocks. A new medium is one section component + one dispatch entry.
- **Acceptance criteria:** The third tab reads "Release Archive" and shows one card per medium; each card navigates to its browser; the upload/edit forms show `ReleaseType` only for `Cut`.
- **9.3.B — `CmsSessionBrowser` + hero-image authoring.**
- **What:** New `CmsSessionBrowser.razor` — a flat list of Session releases (`Medium == Session`) with cover + hero thumbnail, session name, artist; row Edit + hero-image management. Wire the Session upload/edit path to the hero-image upload endpoint (9.2.B).
- **Why:** Sessions are single-track releases with a distinct hero image; the album parent/child expansion of `CmsAlbumBrowser` is the wrong shape for them.
- **Shape:** Reuse `CmsTrackGrid` parameterized by `MediumFilter` where the layout fits; the hero thumbnail is an additive column / thin wrapper, not a forked table. Hero upload reuses the cover-art one-shot pattern against `HeroImageEntryKey`.
- **Acceptance criteria:** Session browser lists only Session releases; uploading a hero image persists it and renders the thumbnail.
- **9.3.C — `CmsMixBrowser` + waveform trigger wiring.**
- **What:** New `CmsMixBrowser.razor` — a flat list of Mix releases (`Medium == Mix`) with an in-grid waveform-generation **status** column (mirroring Phase 8's `HasWaveformProfile` idiom) and a per-row **Generate Waveform** action. Wire the Mix upload to call the server-side waveform trigger (9.2.B) — the CMS never computes or carries the datum.
- **Why:** A Mix without a generated high-res waveform is incomplete; status-in-grid + generate-action is the Phase 8-established pattern for waveform readiness. The CMS has no in-process data layer by convention, so all it does is fire the trigger.
- **Shape:** Upload flow: `UploadTrackAsync``POST api/release/{id}/mix/waveform` (body-less; the API computes and stores server-side, 9.2.B). The per-row Generate action is the same trigger — recovery costs one POST, with no download/recompute/re-upload of the catalogue's longest audio files.
- **Acceptance criteria:** Mix browser lists only Mix releases and shows per-row waveform status; uploading a Mix fires the trigger and the stored high-res waveform appears as generated; the per-row Generate action recovers a missing waveform.
- **Prerequisite:** 9.2.
- **Open questions:**
- **Genre browse fate.** Resolved: the Genre tab slot is taken by Release Archive (Wave 3A as specced); the existing genre browse functionality is deprioritized and stays route-reachable as-is — no active development, no retirement. The team should not remove it.
- **Waveform preprocessor reuse.** Resolved: one server-side parameterized pipeline (player-bar peek = low-res, Mix = high-res; *One source, multiple views*). The `WaveformProfileService` resolution-parameter refactor lands in **Wave 2 with the trigger endpoint (9.2.B)**, not in this wave.
- **Single-track invariant.** Resolved: hard constraint. One track per Session/Mix release is enforced at upload — the CMS form for those media drops the multi-track master list entirely.
**Completion note:** Genre tab in `TrackList.razor` renamed to Release Archive; medium card group (Cut / Session / Mix) implemented with enum-driven dispatch to medium-specific browsers (no hardcoded switches). `ReleaseArchiveBrowser` component renders three cards navigating to `CmsAlbumBrowser`, `CmsSessionBrowser`, `CmsMixBrowser`. `MediumFields` single-dispatch component added with per-medium field groups (`CutFields`, `SessionFields`, `MixFields`) embedded by `TrackNew`, `TrackEdit`, `BatchUpload`, `BatchEdit`, `AlbumHeaderFields`; `ReleaseType` visible only for Cut medium. `CmsSessionBrowser` implemented as flat list of Session releases with cover + hero thumbnail columns; hero-image upload via `POST api/release/{id}/session/hero-image` integrated into upload/edit path. `CmsMixBrowser` implemented as flat list of Mix releases with in-grid waveform status column (mirroring Phase 8's `HasWaveformProfile` pattern) and per-row Generate trigger via `POST api/release/{id}/mix/waveform`. Single-track invariant enforced in `BatchUpload` for Session/Mix. **Known gap:** medium write path (medium field in `POST api/track/upload` and `PUT api/track/meta` requests) not yet implemented — to be spec'd as Phase 9 Wave 5. All acceptance criteria met; Wave 3 completes Phase 9 on the CMS.
---
### 9.2 Wave 2 — API: medium reads + metadata uploads
**Landed:** 2026-06-12 on dev.