docs: Phase 9 Wave 6 landed — move 9.6 from PLAN to COMPLETED
This commit is contained in:
@@ -8,6 +8,39 @@ Newest entries at the top. Group by phase/wave header (mirroring `PLAN.md` / `CM
|
||||
|
||||
## Phase 9 — Release Medium Types
|
||||
|
||||
### 9.6 Wave 6 — Gap Closure
|
||||
|
||||
**Landed:** 2026-06-13 on dev.
|
||||
|
||||
Two functional gaps the landed Phase 9 surface left open. Both are real (medium intent not honoured at a surface that should honour it), neither is debt. **A is a product decision** (which destination the home-page cards take) and is gated on Daniel — its build is one line of markup either way, but the *shape* of the answer is his to pick. **B is clear-cut** (mirror an existing collapse already proven on the upload path into the edit path). A and B are independent; B can land immediately, A waits on the open question below.
|
||||
|
||||
**9.6.A — Home-page editorial cards have no medium destinations**
|
||||
|
||||
- **What:** The three "Music through Every Medium" editorial cards on `Home.razor` (Studio / Live / DJ Mix — landed §8.6) still render as non-navigating `<div>`s. They carry a deferral comment — `@* TODO Phase 3.x: wire each card to its format-filtered browse route once /tracks?format= exists *@` — written before the medium browse routes existed. Today `/cuts`, `/sessions`, `/mixes` are live and working (§9.4); the only thing that points anywhere from this section is the section CTA "Explore the Archive" → `/tracks`. The cards are the most prominent medium framing on the public site and they are dead ends.
|
||||
- **Why it matters:** This section *is* the home page's pitch of the three-medium taxonomy. Leaving the cards inert undercuts the whole Phase 9 narrative — a visitor reads "Studio / Live / DJ Mix," clicks the most prominent thing on the page, and nothing happens. The destinations now exist; the only question is which destination is right. The TODO's `/tracks?format=` premise is very likely **obsolete** — it predates the medium browsers, which already give each card a real home.
|
||||
- **Shape:** Depends on the open question. Either is small:
|
||||
- **(a) Link the three cards to the existing medium browsers** — Studio → `/cuts`, Live → `/sessions`, Mix → `/mixes`. Promote each `.medium-card` `<div>` to an `<a href>` (the §8.6 spec already anticipated this: "promoting to `<a>` later is a one-line change" — the hover styles assume the affordance). Zero new surface; the routes exist today. Removes the stale TODO.
|
||||
- **(b) Build a `/tracks?format=<medium>` filtered gallery first, then point the cards there** — a flat cross-medium gallery pre-filtered by medium (grid/list toggle, the `TracksView` ergonomics), distinct from the medium-specific browsers. Honours the original TODO's literal premise but adds a surface that does not exist yet: a `format`/`medium` query param on `TracksView` + its VM, plus the routing. The cards then deep-link into that one gallery, pre-filtered.
|
||||
- **Acceptance criteria:** Each of the three editorial cards navigates to a live medium destination on click (desktop and mobile); the stale `/tracks?format=` TODO is resolved (removed under (a), or satisfied under (b)); no card remains a dead `<div>`.
|
||||
- **Open question (Daniel — product decision, do not pre-empt):** Should the cards point at the **existing medium browsers** (`/cuts` / `/sessions` / `/mixes`, shape (a)) or at a **new `/tracks?format=` filtered gallery** (shape (b))?
|
||||
- **(a)** is trivial and honest about what the site already offers — the medium browsers are the canonical per-medium surfaces, and a card that says "Studio Releases" landing on `/cuts` is exactly truthful. The TODO that asked for `/tracks?format=` was written before those browsers existed and is plausibly just stale.
|
||||
- **(b)** adds a surface but unifies the browse experience under one flat gallery the visitor can re-filter in place — the card is an entry point into a single explorable gallery rather than three sibling destinations. Worth it only if Daniel wants the flat cross-medium gallery to be the primary public browse model rather than the medium-specific browsers.
|
||||
- **Note:** (a) requires **no new code beyond the three `href`s** (and the `<div>`→`<a>` promotion the §8.6 spec pre-authorised); the `/cuts`, `/sessions`, `/mixes` routes already satisfy it. (b) is a genuine new view. The choice is Daniel's — it is a question of which browse model the home page should funnel into, not an implementation detail.
|
||||
|
||||
**9.6.B — `BatchEdit` single-track form-shape collapse not applied on the edit path**
|
||||
|
||||
- **What:** `BatchUpload.razor` enforces the single-track invariant (§9.3 resolved: Session/Mix are one-track-per-release) by collapsing its multi-track master list to a single row when the medium is Session or Mix — `OnMediumChanged` trims the form to row 1. The edit path `BatchEdit.razor` (`/tracks/album/{AlbumName}/edit`) was not given the same collapse; a code comment flags the deferral. Opening a Session or Mix release in `BatchEdit` today shows the **full multi-track master list** — a form shape that, by the phase's own resolved invariant, should not exist for those media.
|
||||
- **Why it matters:** The edit form contradicts the data model it edits. Sessions and Mixes are single-track by design and the upload path already enforces that; the edit path showing a multi-track list invites an admin to add tracks to a release that is not supposed to have them, and presents an inconsistent authoring experience between create and edit for the same medium. It is the upload-path invariant left half-applied.
|
||||
- **Shape:** Mirror `BatchUpload`'s `OnMediumChanged` collapse logic into `BatchEdit`. When the loaded (or selected) medium is Session or Mix, collapse the master list to a single track row and hide the add-track affordance, exactly as `BatchUpload` does — `BatchUpload.OnMediumChanged` is the reference implementation; reuse its shape rather than authoring a second one (the collapse logic is a candidate to lift into a shared helper or the `MediumFields` dispatch if it reads cleanly, but parity with upload is the requirement, shared extraction is the nicety). The medium selector wiring into `BatchEdit`'s submit path already landed in §9.5.B; this is the form-shape half that did not.
|
||||
- **Acceptance criteria:** Opening a Session or Mix release in `BatchEdit` shows a single-track form with no add-track affordance, matching `BatchUpload` for the same medium; opening a Cut release is unchanged (full multi-track list); switching the medium selector to Session/Mix within `BatchEdit` collapses the list live, the same gesture `BatchUpload` performs.
|
||||
- **Open question:** How should `BatchEdit` render an **existing Session/Mix release that already holds multiple tracks** (e.g. one created before the §9.3 single-track invariant landed, or mis-authored)? Collapsing the form to row 1 would visually hide tracks 2..n without deleting them — the admin sees one track, the DB holds several, and a save could silently orphan the editing of the hidden tracks. Recommend the safe reading: if a non-Cut release loads with >1 live track, do **not** silently collapse — show the full list with an inline warning ("Sessions and Mixes are single-track; this release has N — remove extras to conform") and let the admin reconcile, only enforcing the single-row collapse once the release is already conformant. This keeps the invariant from destroying data it was added after. Flag for Daniel; the collapse-on-conformant-release behaviour (the common case) is unambiguous and can land regardless.
|
||||
|
||||
**Dependency summary for Wave 6:** A and B are independent. B is unblocked and clear-cut (mirror the proven `BatchUpload` collapse). A is blocked only on the Daniel product decision above — once the destination is chosen, its build is trivial. Neither depends on the other.
|
||||
|
||||
**Completion note:** 9.6.A — Home-page editorial cards on `Home.razor` linked to medium-specific browsers (decision (a) implemented): Studio → `/cuts`, Live → `/sessions`, Mix → `/mixes`. Each `.medium-card` `<div>` promoted to `<a href>` navigating to the corresponding route; stale `/tracks?format=` TODO removed. 9.6.B — `BatchEdit.razor` single-track form-shape collapse mirrored from `BatchUpload.OnMediumChanged`: when loaded or selected medium is Session or Mix, master list collapsed to single row with no add-track affordance, matching upload-path invariant. The open question about existing multi-track Session/Mix releases was resolved by Daniel as **straight collapse, no warning path** — Phase 9 is unmerged so zero legacy multi-track data exists; the collapse logic in `OnInitializedAsync` (lines 197–200) and `OnMediumChanged` (lines 143–151) silently trims to one track on load and on selector change with no defensive UI. Wave 6 closes functional gaps in Phase 9 medium taxonomy surface; no regressions, both items clarify intent where taxonomy did not yet reach.
|
||||
|
||||
---
|
||||
|
||||
### 9.4 Wave 4 — Public site: ARCHIVE nav, CUTS / SESSIONS / MIXES, waveform visualizer
|
||||
|
||||
**Landed:** 2026-06-13 on dev.
|
||||
|
||||
@@ -165,33 +165,6 @@ Sequenced as four waves. Wave 1 is a prerequisite for everything; within Waves 2
|
||||
|
||||
Waves 1–5 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.6 Wave 6 — Gap Closure
|
||||
|
||||
Two functional gaps the landed Phase 9 surface left open. Both are real (medium intent not honoured at a surface that should honour it), neither is debt. **A is a product decision** (which destination the home-page cards take) and is gated on Daniel — its build is one line of markup either way, but the *shape* of the answer is his to pick. **B is clear-cut** (mirror an existing collapse already proven on the upload path into the edit path). A and B are independent; B can land immediately, A waits on the open question below.
|
||||
|
||||
**9.6.A — Home-page editorial cards have no medium destinations**
|
||||
|
||||
- **What:** The three "Music through Every Medium" editorial cards on `Home.razor` (Studio / Live / DJ Mix — landed §8.6) still render as non-navigating `<div>`s. They carry a deferral comment — `@* TODO Phase 3.x: wire each card to its format-filtered browse route once /tracks?format= exists *@` — written before the medium browse routes existed. Today `/cuts`, `/sessions`, `/mixes` are live and working (§9.4); the only thing that points anywhere from this section is the section CTA "Explore the Archive" → `/tracks`. The cards are the most prominent medium framing on the public site and they are dead ends.
|
||||
- **Why it matters:** This section *is* the home page's pitch of the three-medium taxonomy. Leaving the cards inert undercuts the whole Phase 9 narrative — a visitor reads "Studio / Live / DJ Mix," clicks the most prominent thing on the page, and nothing happens. The destinations now exist; the only question is which destination is right. The TODO's `/tracks?format=` premise is very likely **obsolete** — it predates the medium browsers, which already give each card a real home.
|
||||
- **Shape:** Depends on the open question. Either is small:
|
||||
- **(a) Link the three cards to the existing medium browsers** — Studio → `/cuts`, Live → `/sessions`, Mix → `/mixes`. Promote each `.medium-card` `<div>` to an `<a href>` (the §8.6 spec already anticipated this: "promoting to `<a>` later is a one-line change" — the hover styles assume the affordance). Zero new surface; the routes exist today. Removes the stale TODO.
|
||||
- **(b) Build a `/tracks?format=<medium>` filtered gallery first, then point the cards there** — a flat cross-medium gallery pre-filtered by medium (grid/list toggle, the `TracksView` ergonomics), distinct from the medium-specific browsers. Honours the original TODO's literal premise but adds a surface that does not exist yet: a `format`/`medium` query param on `TracksView` + its VM, plus the routing. The cards then deep-link into that one gallery, pre-filtered.
|
||||
- **Acceptance criteria:** Each of the three editorial cards navigates to a live medium destination on click (desktop and mobile); the stale `/tracks?format=` TODO is resolved (removed under (a), or satisfied under (b)); no card remains a dead `<div>`.
|
||||
- **Open question (Daniel — product decision, do not pre-empt):** Should the cards point at the **existing medium browsers** (`/cuts` / `/sessions` / `/mixes`, shape (a)) or at a **new `/tracks?format=` filtered gallery** (shape (b))?
|
||||
- **(a)** is trivial and honest about what the site already offers — the medium browsers are the canonical per-medium surfaces, and a card that says "Studio Releases" landing on `/cuts` is exactly truthful. The TODO that asked for `/tracks?format=` was written before those browsers existed and is plausibly just stale.
|
||||
- **(b)** adds a surface but unifies the browse experience under one flat gallery the visitor can re-filter in place — the card is an entry point into a single explorable gallery rather than three sibling destinations. Worth it only if Daniel wants the flat cross-medium gallery to be the primary public browse model rather than the medium-specific browsers.
|
||||
- **Note:** (a) requires **no new code beyond the three `href`s** (and the `<div>`→`<a>` promotion the §8.6 spec pre-authorised); the `/cuts`, `/sessions`, `/mixes` routes already satisfy it. (b) is a genuine new view. The choice is Daniel's — it is a question of which browse model the home page should funnel into, not an implementation detail.
|
||||
|
||||
**9.6.B — `BatchEdit` single-track form-shape collapse not applied on the edit path**
|
||||
|
||||
- **What:** `BatchUpload.razor` enforces the single-track invariant (§9.3 resolved: Session/Mix are one-track-per-release) by collapsing its multi-track master list to a single row when the medium is Session or Mix — `OnMediumChanged` trims the form to row 1. The edit path `BatchEdit.razor` (`/tracks/album/{AlbumName}/edit`) was not given the same collapse; a code comment flags the deferral. Opening a Session or Mix release in `BatchEdit` today shows the **full multi-track master list** — a form shape that, by the phase's own resolved invariant, should not exist for those media.
|
||||
- **Why it matters:** The edit form contradicts the data model it edits. Sessions and Mixes are single-track by design and the upload path already enforces that; the edit path showing a multi-track list invites an admin to add tracks to a release that is not supposed to have them, and presents an inconsistent authoring experience between create and edit for the same medium. It is the upload-path invariant left half-applied.
|
||||
- **Shape:** Mirror `BatchUpload`'s `OnMediumChanged` collapse logic into `BatchEdit`. When the loaded (or selected) medium is Session or Mix, collapse the master list to a single track row and hide the add-track affordance, exactly as `BatchUpload` does — `BatchUpload.OnMediumChanged` is the reference implementation; reuse its shape rather than authoring a second one (the collapse logic is a candidate to lift into a shared helper or the `MediumFields` dispatch if it reads cleanly, but parity with upload is the requirement, shared extraction is the nicety). The medium selector wiring into `BatchEdit`'s submit path already landed in §9.5.B; this is the form-shape half that did not.
|
||||
- **Acceptance criteria:** Opening a Session or Mix release in `BatchEdit` shows a single-track form with no add-track affordance, matching `BatchUpload` for the same medium; opening a Cut release is unchanged (full multi-track list); switching the medium selector to Session/Mix within `BatchEdit` collapses the list live, the same gesture `BatchUpload` performs.
|
||||
- **Open question:** How should `BatchEdit` render an **existing Session/Mix release that already holds multiple tracks** (e.g. one created before the §9.3 single-track invariant landed, or mis-authored)? Collapsing the form to row 1 would visually hide tracks 2..n without deleting them — the admin sees one track, the DB holds several, and a save could silently orphan the editing of the hidden tracks. Recommend the safe reading: if a non-Cut release loads with >1 live track, do **not** silently collapse — show the full list with an inline warning ("Sessions and Mixes are single-track; this release has N — remove extras to conform") and let the admin reconcile, only enforcing the single-row collapse once the release is already conformant. This keeps the invariant from destroying data it was added after. Flag for Daniel; the collapse-on-conformant-release behaviour (the common case) is unambiguous and can land regardless.
|
||||
|
||||
**Dependency summary for Wave 6:** A and B are independent. B is unblocked and clear-cut (mirror the proven `BatchUpload` collapse). A is blocked only on the Daniel product decision above — once the destination is chosen, its build is trivial. Neither depends on the other.
|
||||
|
||||
### 9.7 Wave 7 — Domain Invariant Hardening: per-medium track cardinality
|
||||
|
||||
The single-track-per-release rule for Session/Mix is enforced **only in the CMS form layer today** (the `BatchUpload`/`BatchEdit` master-list collapse, §9.6.B). Below the form there is no enforcement: `UnifiedTrackService.UploadAsync` links a track to a release without checking the release's medium or its existing track count, and §9.5.A's first-upload-authoritative behaviour links a *second* track to an existing `(album, artist)` release with no cardinality gate. A multi-track Session/Mix is therefore reachable via repeated separate uploads or any non-CMS `POST api/track/upload` caller. This wave makes per-medium cardinality a **real domain invariant** rather than a UI convention. Full design — the generalised rule, the enforcement-layer trade-offs, the orphan-avoidance reordering, the relationship to the existing rules, and the back-compat reality — lives in `product-notes/phase-9-medium-cardinality-invariant.md`. One item, gated on one Daniel decision (the open question below).
|
||||
|
||||
Reference in New Issue
Block a user