732 lines
48 KiB
Markdown
732 lines
48 KiB
Markdown
# Phase 9 — Wave 8: Remediation
|
||
|
||
Status: **spec** (CMS + public tracks — open questions resolved 2026-06-13).
|
||
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 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 (with **8.M** split off it for the legacy-form retirement), 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 or 8.M.
|
||
|
||
---
|
||
|
||
## 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.
|
||
|
||
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, 8.L)** — compose the Session hero image into the upload form
|
||
(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
|
||
all-**releases** view (release-cardinal — decided H2) *is* the archive. Slim the nav, drop GENRES
|
||
(nav-only), fix the stuck-open popover.
|
||
|
||
**Out of Phase-9-completion scope (but documented in full):** the **Mix Visualizer redesign (8.K)**.
|
||
The interview has run; `phase-9-mix-visualizer-redesign.md` is now a complete, implementation-ready
|
||
design spec. Phase 9 closes without it; it runs as a post-Phase-9 wave.
|
||
|
||
---
|
||
|
||
## 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 `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
|
||
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
|
||
Add-Track that pre-selects Session should land the admin on a form that *has* the hero field).
|
||
|
||
---
|
||
|
||
### 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.
|
||
|
||
**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.**
|
||
- 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.
|
||
- **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.
|
||
- A Session uploaded with no hero image still succeeds 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.
|
||
|
||
---
|
||
|
||
### 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.
|
||
|
||
---
|
||
|
||
### 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.
|
||
|
||
**Sync posture — DECIDED (Daniel, 2026-06-13): keep them synced.** 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. 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`) and `TrackEdit.razor` (`/tracks/{Id:long}`) — the legacy
|
||
single-track forms.** Both surface the separate `Track Name` / `Album` split and a `MediumFields`
|
||
selector, so both carry the same two-name redundancy on the single-track path. **These are not patched
|
||
in-place for 8.L. Their disposition is consolidation — see 8.M (legacy-form retirement).** Daniel
|
||
(2026-06-13): "I would prefer to consolidate the forms and reduce the code surface if possible." The
|
||
decision is to fold their responsibility into the batch forms and retire the legacy pair, not to
|
||
re-plumb a name-collapse into forms slated for removal. 8.L therefore touches **only** the batch
|
||
forms (`BatchUpload` / `BatchEdit` via `BatchTrackDetail`); the legacy forms are out of 8.L's scope
|
||
and handled by 8.M.
|
||
|
||
*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 (8.L proper):** the name-collapse is **entirely CMS-side** and concentrated in the
|
||
two batch forms (`BatchUpload`, `BatchEdit`) via the shared `BatchTrackDetail`. The legacy single-track
|
||
forms (`TrackNew`, `TrackEdit`) are **no longer part of 8.L** — their two-name redundancy is resolved by
|
||
retiring them outright (8.M), not by patching a collapse into them. 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 are **out of 8.L scope** — their consolidation is 8.M
|
||
(legacy-form retirement). 8.L lands independently of 8.M; the name-collapse on the batch forms does
|
||
not wait on the legacy retirement.
|
||
- 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), the public cluster, and **8.M** (8.L
|
||
lands on the batch forms whether or not the legacy forms are retired).
|
||
|
||
---
|
||
|
||
### 8.M — Retire the legacy single-track forms; consolidate onto the batch forms
|
||
|
||
**Goal.** Retire `TrackNew` (`/tracks/new`) and `TrackEdit` (`/tracks/{Id:long}`) as the single-track
|
||
authoring path. Their add/edit responsibility is **absorbed by `BatchUpload` / `BatchEdit`**, whose
|
||
single-track branch already handles Session/Mix. This **reduces the duplicate form surface** — Daniel
|
||
(2026-06-13): "I would prefer to consolidate the forms and reduce the code surface if possible." The
|
||
single-track authoring path becomes the batch form's single-track branch; the legacy routes redirect to
|
||
(or are replaced by) the batch routes.
|
||
|
||
**User-visible change.** Adding or editing a single track no longer opens a distinct single-field form.
|
||
The same batch form that handles releases handles the one-track case — one form for both, no second
|
||
authoring surface to maintain. (Cuts already author via the batch forms; this brings the Session/Mix and
|
||
single-Cut-track cases onto the same path.)
|
||
|
||
**Feasibility read (against live source, 2026-06-13). Verdict: retirement-with-reconciliation — feasible,
|
||
but not a pure delete. One real gap must be closed first.**
|
||
|
||
What the batch forms already cover that makes this viable:
|
||
- `BatchUpload` already *is* the upload path for every medium, with a dedicated single-track branch
|
||
(`_medium != Cut` renders a single WAV slot + name field, lines ~53–70). `TrackNew` adds nothing the
|
||
batch upload doesn't already do — same fields (name, artist, album/release, genre, release date,
|
||
medium, cover art), same `UploadTrackAsync` call, same Mix-waveform trigger, same cover-link follow-up.
|
||
**`TrackNew` is a clean retirement** (see route note below).
|
||
- `BatchEdit` already collapses to a single row for Session/Mix (§9.6.B; `AllowNewTracks` gated on
|
||
`_medium == Cut`, `OnMediumChanged` trims to one row) and carries the same edit fields, delete, and
|
||
cover handling `TrackEdit` has.
|
||
|
||
The gap that must reconcile — **addressing model**:
|
||
- `TrackEdit` is addressed **by track id** (`/tracks/{Id:long}`). `BatchEdit` is addressed **by release
|
||
title** (`/tracks/album/{AlbumName}/edit`) and loads the *whole release*. They key off different
|
||
things. For Session/Mix this is harmless (one track = one release; the names are about to be synced by
|
||
8.L anyway), but for a **single Cut track** opened from Track mode's per-row Edit, routing to
|
||
`BatchEdit` would open the *entire parent release*, not just that track. That is a behaviour change,
|
||
not a 1:1 swap.
|
||
- **The one live inbound link to a legacy form is `CmsTrackGrid.razor` line ~82** — Track mode's per-row
|
||
Edit button: `Href="/tracks/{context.Id}"` → `TrackEdit`. This is the surface that must be reconciled:
|
||
either Track mode's row-edit retargets to a batch-edit-by-release (accepting that editing a track
|
||
opens its release), or `BatchEdit` gains an address-by-track-id entry that pre-selects the row. Either
|
||
is a real decision, not a mechanical rename.
|
||
- `TrackNew` (`/tracks/new`) has **no live inbound nav link** in source — every "Add Track" button
|
||
points at `/tracks/upload` (`CmsTrackGrid` line ~16, the 8.E plan). So `TrackNew`'s route can be
|
||
dropped or made a redirect to `/tracks/upload` with **zero** caller changes.
|
||
|
||
Route/redirect implications:
|
||
- `/tracks/new` → redirect to (or replace with) `/tracks/upload`. No callers to update.
|
||
- `/tracks/{Id:long}` → either redirect to a release-scoped batch edit (and retarget `CmsTrackGrid`'s
|
||
row Edit), or retain a thin track-addressed entry into the batch edit. **This is the reconciliation
|
||
call** and it is the crux of whether this lands cleanly.
|
||
|
||
**Verdict, stated plainly:** `TrackNew` is a **clean retirement**. `TrackEdit` is a
|
||
**retirement-with-reconciliation** — feasible, but it requires a decision on the single-Cut-track edit
|
||
affordance (open the whole release vs. address a single track within the batch edit) and a retarget of
|
||
`CmsTrackGrid`'s one inbound link. Not blocked; not free.
|
||
|
||
**Acceptance criteria.**
|
||
- `/tracks/new` no longer renders a distinct form; the add-single-track path is `BatchUpload`'s
|
||
single-track branch (route removed or redirected to `/tracks/upload`).
|
||
- Editing a single track no longer opens `TrackEdit`'s distinct form; the edit path is `BatchEdit`'s
|
||
single-track branch. `CmsTrackGrid`'s per-row Edit routes to the reconciled destination.
|
||
- The single-Cut-track edit affordance behaves per the reconciliation decision (documented at
|
||
implementation time): either it opens the parent release in `BatchEdit`, or `BatchEdit` accepts a
|
||
track-addressed entry that pre-selects the row.
|
||
- No dangling references to `TrackNew` / `TrackEdit` routes remain in live navigation.
|
||
- Net CMS form-code surface is reduced (two fewer routable forms to maintain).
|
||
|
||
**Architectural vs. mechanical (assessment).** **Architectural — staff-engineer territory.** This is not
|
||
a localized edit: it changes the CMS route map (two routes removed/redirected), removes two routable
|
||
components, and — most importantly — **changes the single-track edit addressing model** (track-id vs.
|
||
release-title), which carries a navigation-behaviour decision (does editing a Cut track open its whole
|
||
release?). That decision touches how Track mode's per-row Edit behaves and may require `BatchEdit` to
|
||
accept a new addressing mode. Route-map changes + component removal + a navigation-model decision is
|
||
squarely staff-engineer scope, not a maintenance pass.
|
||
|
||
**Dependencies.** Independent of 8.L (8.L lands the batch-form name-collapse regardless). Coordinates
|
||
with **8.E** (the Add-Track buttons already target `/tracks/upload`, so 8.E and 8.M agree on the upload
|
||
route). Best sequenced **after** 8.L so the batch single-track branch is already name-consolidated when
|
||
it becomes the sole single-track path. Lower priority than the name-collapse — see split rationale in §5.
|
||
|
||
---
|
||
|
||
## 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.
|
||
|
||
**Decision (H2 — Daniel, 2026-06-13): the archive is release-cardinal.** Build a **new searchable
|
||
all-*releases* browser at `/archive`** — search + medium/genre filter, release cards (cover, title,
|
||
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 cascade from H2 (decided):**
|
||
- **`/tracks` (`TracksView`, track-cardinal) is no longer the archive.** It is not what ARCHIVE points
|
||
at. Its fate: keep the route reachable (no hard 404), but it is demoted from the nav — the archive is
|
||
the release-cardinal browser, and the flat track gallery is not the primary browse surface anymore.
|
||
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.
|
||
|
||
**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.
|
||
|
||
**Mobile ARCHIVE → the searchable browser (Daniel, 2026-06-13).** With `/archive` becoming the
|
||
searchable browser, **mobile ARCHIVE goes straight to that browser** (the medium modes are reachable
|
||
via the in-page medium filter). The medium links also live in the hamburger sub-list under ARCHIVE
|
||
(8.I). **The three-card overview is fully retired** — there is no card-overview destination on any
|
||
breakpoint.
|
||
|
||
**Acceptance criteria.**
|
||
- `/archive` renders a **release-cardinal** searchable browse surface (search field + medium/genre
|
||
filter), not the three-card overview.
|
||
- The surface covers **all releases** (every medium), with a medium filter (ALL + per-medium) and search
|
||
within.
|
||
- 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`).
|
||
|
||
**Dependencies.** Gates **8.I** (8.I links ARCHIVE to this browser). Independent of 8.J. No longer gated
|
||
on a framing decision — H2 is decided.
|
||
|
||
---
|
||
|
||
### 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 — remove the nav link only, for now (Daniel, 2026-06-13).** Drop the GENRES menu item. **Keep
|
||
the `/genres` route and `GenresView` reachable** — no active development, no hard removal. This is the
|
||
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.**
|
||
- Above the medium breakpoint, ARCHIVE and CUTS / SESSIONS / MIXES appear as appbar links.
|
||
- 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`).
|
||
- **GENRES no longer appears in the nav; `/genres` + `GenresView` remain reachable by URL** (nav-only
|
||
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).
|
||
|
||
---
|
||
|
||
### 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 `[post-Phase-9, design-complete]`
|
||
|
||
**Out of Phase-9-completion scope (Daniel, 2026-06-13).** "Visualizer is outside the scope of phase 9
|
||
but we must document it now in complete detail." Phase 9 can close **without** 8.K. The interview has
|
||
run, and `product-notes/phase-9-mix-visualizer-redesign.md` is now a **finished, implementation-ready
|
||
design spec** — no longer a question set. A future wave can be dispatched straight from it.
|
||
|
||
Headline of the captured design: the visualizer becomes a **windowed, playback-coupled, bottom-to-top
|
||
scrolling waveform** — a musical-score-going-by treatment showing only the currently-playing region.
|
||
Zoom couples to apparent scroll speed (Guitar-Hero model: zoomed in = shorter time-span fills the
|
||
screen = faster apparent motion), with a hard anchor that **at maximum zoom exactly one quarter note is
|
||
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 is the one Wave 8 track that does not gate Phase 9 completion.** It is design-complete and
|
||
sequenced as a post-Phase-9 implementation wave.
|
||
|
||
---
|
||
|
||
## 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), **8.L** (single-track name consolidation) —
|
||
**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.
|
||
- **8.M** (legacy single-track form retirement) — **architectural** (route-map change + component
|
||
removal + single-track edit addressing decision; staff-engineer scope). Independent of 8.L for build;
|
||
best sequenced **after** 8.L so the batch single-track branch is already name-consolidated when it
|
||
becomes the sole single-track path. **Lower priority than 8.L** — see split rationale below.
|
||
|
||
**8.L / 8.M split rationale.** These were one item ("consolidate the names"); they are now two because
|
||
they have different cost and risk. **8.L is mechanical-to-moderate, self-contained, and high-value** —
|
||
collapse two name inputs to one on the batch forms via a `BatchTrackDetail` flag, sync the derived track
|
||
name on save. It touches no routes and no component graph. **8.M is architectural** — retiring
|
||
`TrackNew`/`TrackEdit` means a route-map change, two component removals, and a real decision about
|
||
whether editing a single Cut track opens its whole release (the track-id vs. release-title addressing
|
||
gap). Bundling them would gate the cheap, safe name-collapse behind the route/navigation decision. Split
|
||
so **8.L lands independently and immediately**, and **8.M follows when the addressing reconciliation is
|
||
decided**. They share intent ("one name, fewer forms") but not blast radius.
|
||
|
||
**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 = release-cardinal searchable browser) — **decided (H2)**; build the new browser.
|
||
- **8.I** (nav slim + GENRES out + `/tracks` demoted) — depends on 8.H (ARCHIVE target); coordinates
|
||
with 8.J.
|
||
|
||
**Mix Visualizer:**
|
||
- **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), then 8.L
|
||
(name-collapse, after 8.G). Then the CMS spine (8.B → 8.A → 8.C/8.E), folding 8.F into the Session-form
|
||
work alongside 8.E/8.L. On the public side, 8.H (the new release-cardinal archive) → 8.I. **8.M**
|
||
(legacy-form retirement) sequences after 8.L and is **not a Phase-9-completion gate** — it is a
|
||
code-surface-reduction follow-on, not a taxonomy-reach correction; it can land in Wave 8's tail or just
|
||
after. **Phase 9 closes when 8.A–8.J + 8.L land; 8.K and 8.M are excluded from the completion gate**
|
||
(8.K is a post-Phase-9 design-complete wave; 8.M is a consolidation follow-on that can trail).
|
||
|
||
---
|
||
|
||
## 6. Decisions — all resolved (Daniel, 2026-06-13)
|
||
|
||
The open questions that gated this wave are answered. Recorded here as the decision log; baked into the
|
||
acceptance criteria above.
|
||
|
||
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) Single-track name sync → CONFIRMED synced.** The derived track name is kept **synced** to
|
||
the Release Name for Session/Mix on both create and edit, so they can never diverge. (Daniel,
|
||
2026-06-13 — was the one open recommendation; now decided.)
|
||
7. **(8.M) Legacy single-track forms → CONSOLIDATE / retire.** Daniel (2026-06-13): "I would prefer to
|
||
consolidate the forms and reduce the code surface if possible." `TrackNew`/`TrackEdit` are folded
|
||
into the batch forms and retired, not patched in place. Split from 8.L (the name-collapse) because
|
||
8.M is architectural (route map + addressing model) while 8.L is self-contained. 8.M is not a
|
||
Phase-9-completion gate.
|
||
8. **(8.K) Mix Visualizer → out of Phase-9 scope, design-complete.** Documented in full now; built
|
||
later. Phase 9 closes without it.
|