docs(plan): move Phase 6 CMS Enhancements (6.1 dashboard, 6.3 batch upload) to COMPLETED.md
This commit is contained in:
@@ -6,6 +6,65 @@ Newest entries at the top. Group by phase/wave header (mirroring `PLAN.md` / `CM
|
||||
|
||||
---
|
||||
|
||||
## Phase 6 — CMS Enhancements
|
||||
|
||||
### 6.3 Batch Upload Page
|
||||
|
||||
**Landed:** 2026-06-11 on dev.
|
||||
|
||||
- **What:** Replace the single-track form at `/tracks/new` with a two-panel batch upload page that uploads many WAVs in one session under a shared album header.
|
||||
- **Why:** Uploading an album one track at a time is the current reality — re-entering album, genre, release date, cover art, and artist on every track. Batch upload makes "add a release" a single operation: set the shared header once, queue the tracks, submit. This is the dominant ingestion shape for the collective (releases, not loose singles).
|
||||
- **Shape:**
|
||||
- **Route:** New page at **`/tracks/upload`**. Justification: `/tracks/new` reads as "new single track" and the edit route is `/tracks/{id}`; `/tracks/upload` names the operation (batch ingestion) without colliding with the id-parameterised edit route. Repoint the "Add Track" button in `TrackList.razor` (currently `Href="/tracks/new"`) to `/tracks/upload`. Whether `/tracks/new` is retired or left as a redirect is staff-engineer's call; the committed change is that the button goes to the batch page.
|
||||
- **Data model change — `ReleaseType`:** Add a `ReleaseType` enum to `DeepDrftModels` (`enum ReleaseType { Single, EP, Album }`). Enum over string: three fixed values, and it gates UI (selector) and future grouping logic — a free-text column invites typos. Add a `ReleaseType` property to **`TrackEntity`** and **`TrackDto`**. Decide nullability: recommend **non-null with a default of `Single`** so existing rows backfill cleanly to a sensible value (a release of one track is a single) and the column is never null. This ripples to `TrackConfiguration` (EF mapping — store as string via `HasConversion<string>()` for readable DB values, or as int; recommend string for legibility), `TrackConverter` (assign on round-trip), and the upload/update service signatures. **An EF migration is required** — author it via `dotnet ef migrations add`, never by hand.
|
||||
- **Data model change — `TrackNumber`:** Add a `TrackNumber` property (type `int`, **1-based, non-null**) to **`TrackEntity`** and **`TrackDto`** to store per-track ordinal position within a release. This ripples through `TrackConfiguration` (EF mapping) and `TrackConverter` (assign on round-trip) the same way `ReleaseType` does. **A second EF migration is required** — author it via `dotnet ef migrations add`, never by hand. May be combined into a single migration with the `ReleaseType` change — staff-engineer's call on whether to combine or keep separate.
|
||||
- **Shared-vs-per-track field split:**
|
||||
- *Shared (header strip, applied to every track in the batch):* album name, artist, album cover image (single upload), genre, release date, and `ReleaseType`. One album per batch — the entire batch is one release, and all release-level fields live in the header.
|
||||
- *Per-track (right detail panel):* track name, the individual WAV file, and that row's upload status.
|
||||
- **Layout (two-panel under a header strip):**
|
||||
- **Header strip** (full width, top): album name, artist `MudTextField`, single cover-art `InputFile` (reuse the `MudField` cover-art pattern from `TrackNew`, including the upload-on-submit behaviour), genre `MudTextField`, release-date field, and `ReleaseType` `MudSelect`. These bind to a single batch-header model.
|
||||
- **Left panel** (track queue): an ordered list of queued tracks; the row order *is* the release track order and reflects each track's `TrackNumber`. Each row shows track name, a reorder affordance (up/down `MudIconButton`s are the low-risk choice; drag-and-drop is a nice-to-have — see open questions), a remove button, and a per-row status indicator (queued / uploading / done / failed). A `+`/`InputFile` (with `multiple`) at the top or bottom of the list adds WAV files; each added file becomes a row with track name defaulted from the filename (sans extension). On submit, each track is assigned its `TrackNumber` (1-based) from its position in the list.
|
||||
- **Right panel** (selected-track detail): when a row is selected, show its editable fields — track name and the WAV file name/size/status. Selecting a different row swaps the detail.
|
||||
- **Add-files behaviour:** `InputFile multiple` → append a row per file. Default track name = filename without extension. New rows append to the end of the list, taking the next ordinal position. Keep the 1 GB per-file ceiling and the `.wav` validation from `TrackNew`.
|
||||
- **Submit behaviour:** Sequential, one request at a time — reuse the existing single-track upload path (`CmsTrackService.UploadTrackAsync`) in a loop. This mirrors the deliberately-sequential waveform backfill in `TrackList.GenerateAllMissing` ("one request at a time so a large backfill does not flood the API"). Per-track progress: each left-panel row reflects its state as the loop advances (`StateHasChanged` between rows). Cover-art upload happens **once** before the loop (upload the image, get the entry key, then pass/link it to every track) — do not re-upload the cover per track. On completion, snackbar a summary (`uploaded N, M failed`) and navigate to `/tracks`. Partial failure: completed tracks stay persisted; failed rows remain visible with their error so the admin can retry just those — do **not** roll back the batch.
|
||||
- **CmsTrackService surface:** No new method strictly required — the loop calls the existing `UploadTrackAsync` per track and the existing image upload/link path per batch. `UploadTrackAsync`'s signature gains `releaseType` and `trackNumber` parameters (ripples from the data-model change). If the cover-link follow-up (the `UpdateAsync` step `TrackNew` does today) is kept per track, that's existing surface too.
|
||||
- **API surface:** No new endpoints. Existing `POST api/track/upload` (per track) and `POST api/image/upload` (once per batch) cover it. `api/track/upload` and the metadata update endpoints gain `releaseType` and `trackNumber` in their payloads as a consequence of the entity change.
|
||||
- **Components:** `BatchUpload.razor` (page + header strip + orchestration), and reasonably a `BatchTrackRow` model class plus left-panel/right-panel as child components or inline sections — staff-engineer's structural call.
|
||||
- **Constraint — dual-write orphan risk:** Each track inherits the existing dual-write hazard (audio lands in the vault, SQL persist may fail → orphaned audio, no rollback). Batch upload *multiplies the exposure* (N tracks per session instead of one). The mitigation is **Phase 4.3 (dual-write rollback / dead-letter log)** — not a blocker for this feature, but this is the strongest argument yet for landing 4.3. Flag it as a known constraint; do not attempt per-batch transactional rollback (the dual-database split can't give it).
|
||||
- **Prerequisites:**
|
||||
- `ReleaseType` enum + `TrackNumber` field + `TrackEntity`/`TrackDto` changes + EF migration(s) must land first (it's the data-model floor for the whole feature, and ripples through `TrackConfiguration`/`TrackConverter`/service signatures). Could be a separate prep commit before the page work.
|
||||
- **Not blocked by** Phase 4.3, but 4.3 is the right mitigation for the amplified orphan risk and is worth sequencing alongside.
|
||||
- **Resolved (no longer open):**
|
||||
- **One album per batch.** The whole batch is one release; album name and all release-level fields (artist, genre, release date, `ReleaseType`, cover art) live in the shared header strip. A batch never mixes albums.
|
||||
- **Track ordinals are persistent** — `TrackNumber` (int, 1-based, non-null) stores per-track position within a release. The left-panel row order reflects `TrackNumber`, and each track is assigned its ordinal from its list position on submit.
|
||||
|
||||
**Completion note:** `BatchUpload.razor` page implemented at `/tracks/upload`; two-panel layout with header strip (shared album/artist/genre/release-date/cover-art/release-type fields) and left queue + right detail sections for per-track track name and file selection. Sequential upload loop via existing `CmsTrackService.UploadTrackAsync`. Cover-art uploaded once at start; per-track progress reflected in left-panel status indicators. `TrackList.razor` "Add Track" button repointed to `/tracks/upload`. `ReleaseType` enum and `TrackNumber` int field added to `TrackEntity`, `TrackDto`, `TrackConfiguration`, `TrackConverter`, and EF migrations authored. `UploadTrackAsync` signature updated with `releaseType` and `trackNumber` parameters.
|
||||
|
||||
---
|
||||
|
||||
### 6.1 CMS Home Page — catalogue summary dashboard
|
||||
|
||||
**Landed:** 2026-06-11 on dev.
|
||||
|
||||
- **What:** Replace the redirect-to-`/tracks` at `Index.razor` (route `/`) with a real dashboard showing a grid of summary cards: total tracks, distinct albums, distinct genres.
|
||||
- **Why:** Quick orientation for the CMS admin — at-a-glance catalogue health on landing, instead of dropping straight into the table. First thing the admin sees, so it carries the bold DeepDrft palette rather than a conservative admin look.
|
||||
- **Shape:**
|
||||
- **Route / component:** Keep `Index.razor` at `/`; remove the `OnInitialized` redirect and render the dashboard. The CMS nav lands here; `/tracks` remains reachable from the nav and from the cards.
|
||||
- **UI:** A responsive `MudGrid` of three `MudCard`s (Tracks / Albums / Genres). Each card: an icon (`LibraryMusic`, `Album`, `Category` or similar), the metric as a large `Typo.h2`/`h3` number, and a label. Cards are clickable (`@onclick` → `Nav.NavigateTo`). Lean into the active MudBlazor palette — `Color.Primary`/`Color.Secondary` fills or accent borders, generous elevation — this is the visual-punch surface, not a muted KPI strip. Loading state: skeleton or per-card `MudProgressCircular` while the three fetches resolve. Each card fetches independently so one slow/failed call doesn't blank the others; a failed card shows a "—" with a retry affordance rather than collapsing the grid.
|
||||
- **Card navigation (Phase 6 scope):** All three cards navigate to `/tracks` (the track maintenance page). **Per-album / per-genre pre-filtering is deferred** — see 6.2. Ship the cards as plain links to `/tracks` now.
|
||||
- **Data model:** No entity changes. `AlbumSummaryDto` and `GenreSummaryDto` already exist in `DeepDrftModels`.
|
||||
- **API surface:** No new API endpoints. The three numbers are already available:
|
||||
- **Albums count** = length of `GET api/track/albums` (exists, unauthenticated, returns `List<AlbumSummaryDto>`).
|
||||
- **Genres count** = length of `GET api/track/genres` (exists, unauthenticated, returns `List<GenreSummaryDto>`).
|
||||
- **Tracks count** = `TotalCount` from `GET api/track/page` (exists) requested with `pageSize=1` (cheapest paged call that still returns the total).
|
||||
- **CmsTrackService surface (new methods):** `ICmsTrackService` does not currently expose albums/genres. Add three thin proxy methods mirroring the existing pattern (e.g. `GetAlbumSummariesAsync`, `GetGenreSummariesAsync`, and a `GetTrackCountAsync` that calls `page?pageSize=1` and returns `TotalCount`). These are the only new code on the service. No controller work.
|
||||
- **Components:** `Index.razor` (dashboard host) plus, optionally, a small `SummaryCard.razor` for the repeated card — worth extracting given three near-identical cards, but staff-engineer's call.
|
||||
- **Prerequisites:** None. All backing endpoints and DTOs exist.
|
||||
|
||||
**Completion note:** `DeepDrftManager/Components/Pages/Index.razor` redesigned as a 3-card dashboard grid (Tracks / Albums / Genres counts) with independent per-card fetches. Three new `ICmsTrackService` proxy methods (`GetAlbumSummariesAsync`, `GetGenreSummariesAsync`, `GetTrackCountAsync`) wired to existing public API endpoints. Cards navigate to `/tracks` on click. Failed cards show "—" fallback; each card loads independently.
|
||||
|
||||
---
|
||||
|
||||
## Phase 1.1 — Extended WAV format support
|
||||
|
||||
**Status:** Fully landed on 2026-06-10 (IEEE Float SubFormat 0x0003 and Padded 24-in-32 container support implemented, tests passing).
|
||||
|
||||
Reference in New Issue
Block a user