diff --git a/COMPLETED.md b/COMPLETED.md index 9173420..5d87e32 100644 --- a/COMPLETED.md +++ b/COMPLETED.md @@ -6,7 +6,17 @@ Newest entries at the top. Group by phase/wave header (mirroring `PLAN.md` / `CM --- -## Phase 11 — Public Site Enhancements +## Phase 11 — Public Site Enhancements (complete — all tracks 11.A–11.H landed 2026-06-16) + +### 11.H — release `EntryKey` identifiers (terminal public-site wave) + +**Landed:** 2026-06-16 on dev. + +- **What:** Front the release `long` PK with an app-minted GUID-string `EntryKey` column — the same pattern `TrackEntity.EntryKey` uses. `ReleaseEntity.EntryKey` is `required string`, minted as `Guid.NewGuid().ToString()` at the `FindOrCreateRelease` path; `ReleaseDto.EntryKey` mirrors it; `TrackConverter` round-trips it. The public addressing surface was re-typed from `long` to the `EntryKey` string handle: detail routes (`/cuts`, `/sessions`, `/mixes`, and the `/tracks/{id}` redirect), `ReleaseRoutes.DetailHref`, `SharePopover.ReleaseId`, the public read path (`IReleaseDataService.GetByEntryKey`), and the public release API (`GET api/release/{entryKey}`, the mix waveform endpoint). The `releaseId` track-page query is resolved client-side from the EntryKey-loaded release and stays `long` (never enters a navigable URL). The internal `long` PK and all internal FKs (`TrackEntity.ReleaseId`, `SessionMetadata.ReleaseId`, `MixMetadata.ReleaseId`) are unchanged — DB-only, unused by the app. ApiKey-gated CMS endpoints stay on the int PK. EF migration `20260616210143_AddReleaseEntryKey` authored; **not yet applied** (Daniel-gated; must follow 11.G's `20260616035252_AddReleaseDescription` in apply order). The migration adds the `entry_key` column, backfills a unique GUID string per existing release row at migration time, then sets NOT NULL + unique index. +- **Why:** The release `long` PK was leaking into navigable public URLs (`/cuts/{long}`, `/sessions/{long}`, `/mixes/{long}`), exposing sequential internal IDs and making public addresses dependent on DB identity. An app-minted opaque GUID handle (the pattern already established by `TrackEntity.EntryKey`) decouples the public addressing surface from the storage PK, enables backfilling existing rows without a dev reset, and completes the commitment-9 scope of Phase 11. +- **Shape:** `ReleaseEntity.EntryKey` (`required string`) in `DeepDrftModels/Entities/`; `ReleaseConfiguration` adds the `entry_key` column config + unique index. `ReleaseDto.EntryKey` in `DeepDrftModels/DTOs/`. `TrackConverter` maps EntryKey on both read and write paths. `FindOrCreateRelease` (`DeepDrftData/TrackManager.cs`) mints `Guid.NewGuid().ToString()` on new-release creates. Public API route params re-typed to string EntryKey: `GET api/release/{entryKey}` + mix waveform endpoint. `IReleaseDataService.GetByEntryKey` (public read path). Detail page routes (`/cuts/{entryKey}`, `/sessions/{entryKey}`, `/mixes/{entryKey}`), `ReleaseRoutes.DetailHref`, `SharePopover.ReleaseId`, `TrackRedirect.razor`. Migration `20260616210143_AddReleaseEntryKey` authored but not applied. + +--- ### 11.D — Archive filters in the URL diff --git a/PLAN.md b/PLAN.md index 5147c1c..f201054 100644 --- a/PLAN.md +++ b/PLAN.md @@ -246,7 +246,7 @@ Sequenced as **eight waves**; the critical path is `11.A → 11.B → 11.C → 1 - **11.G — release Description schema slice.** New `ReleaseEntity.Description` column + EF migration (**Daniel-gated apply**), `ReleaseDto` mirror, `TrackConverter` round-trip, write-path plumbing (`UpdateTrackMetadataRequest` + upload form + the unified services, threaded wherever `Genre` is), CMS `AlbumHeaderFields` multiline input (§3d). **Free-floating, can start cold day one** — the only gate is Daniel's migration go-ahead. The **detail-page render is NOT in this wave**: the Cut text block rides 11.A, the Session/Mix block is a small additive touch to those existing pages. Both degrade cleanly (null Description renders nothing), so render & schema can land in either order. - **11.H — release GUID identifiers (terminal public-site wave).** Front the release `long` PK with an app-minted GUID-string `EntryKey` column — the **same pattern tracks use** (`TrackEntity.EntryKey` is `required string`, app-minted `Guid.NewGuid().ToString()`, keeping the int PK private). New `ReleaseEntity.EntryKey` (`string`, unique index, minted at `FindOrCreateRelease`) + EF migration that **backfills a GUID-string `EntryKey` for every existing release row at migration time** (**Daniel-gated apply**); `ReleaseDto.EntryKey`; `TrackConverter` round-trip; **re-type the public addressing surface from `long` to the `EntryKey` handle** — detail routes (`:long`→`{EntryKey}`), the `/tracks/{id}` redirect, `ReleaseRoutes.DetailHref`, `SharePopover.ReleaseId`, the public read path, and the public release API params (`GET api/release/{id}` + the `releaseId` track-page query). Internal FKs (track→release, satellite→release), the `long` int PK (unused by the app), and the ApiKey-gated CMS endpoints **stay on the int**. **Depends on 11.B (landed), 11.C, 11.D, 11.E** — it sweeps the routes/resolver/share/cards those waves create or edit, so it is the **last** public-site wave (spec §3e.7). **Gating decision (Daniel, spec §3e.5(1)) — RESOLVED (additive `EntryKey`, track-pattern):** additive app-level GUID-string `EntryKey` column matching tracks; the `long` PK stays DB-only and unused by the app; existing rows are backfilled at migration time (not a dev reset). Daniel's rationale (2026-06-16): "long at the DB level with an app-level guid `EntryKey` for the releases just like tracks; PK is not used by the app; migrate the existing data to provide the entry key at migration time." The true PK retype is **declined** (framework fork of `Cerebellum.BlazorBlocks.Models` — `BaseEntity.Id` hardwired `long` — plus full FK rewrite; recorded as considered-and-declined per file convention). Still open: raw-GUID URL (recommended) vs. slug, and migration ordering after 11.G's snapshot. -**Landed:** 11.A (2026-06-16); 11.F (2026-06-16); 11.G (2026-06-16); 11.B (2026-06-16); 11.C (2026-06-16); 11.E (2026-06-16); 11.D (2026-06-16). The §3.4 PlayAlbum→`IQueueService` seam (deferred in 11.A, awaiting 11.F) is now closed: `CutDetail.razor` consumes the cascaded `IQueueService` — header Play calls `Queue.PlayRelease(ViewModel.Tracks, 0)`, per-row play calls `Queue.PlayRelease(ViewModel.Tracks, index)`, currently-playing row toggles play/pause, null-safe fallback to `SelectTrackStreaming` when the queue cascade is absent (2026-06-16). Migration `20260616035252_AddReleaseDescription` authored but not yet applied (Daniel-gated). Track 11.H remains open (the active terminal wave). +**Landed:** 11.A (2026-06-16); 11.F (2026-06-16); 11.G (2026-06-16); 11.B (2026-06-16); 11.C (2026-06-16); 11.E (2026-06-16); 11.D (2026-06-16); 11.H (2026-06-16). The §3.4 PlayAlbum→`IQueueService` seam (deferred in 11.A, awaiting 11.F) is now closed: `CutDetail.razor` consumes the cascaded `IQueueService` — header Play calls `Queue.PlayRelease(ViewModel.Tracks, 0)`, per-row play calls `Queue.PlayRelease(ViewModel.Tracks, index)`, currently-playing row toggles play/pause, null-safe fallback to `SelectTrackStreaming` when the queue cascade is absent (2026-06-16). **All Phase 11 tracks (11.A–11.H) are now landed; Phase 11 is complete.** Two release-table migrations are authored but not yet applied (Daniel-gated, apply in author order): `20260616035252_AddReleaseDescription` (11.G) then `20260616210143_AddReleaseEntryKey` (11.H). **Dependency shape:** `11.A → 11.B → 11.C → 11.H`; `11.B → 11.E`; **11.D, 11.F, 11.G parallel** (11.D coordinates with 11.C on `ArchiveView`; 11.F's "play album" is consumed by 11.A; 11.G's Description render rides 11.A + a Session/Mix touch, degrading on null). **11.H is terminal** — it re-types the public release-addressing surface (routes, `ReleaseRoutes`, `SharePopover`, cards, public API params) that 11.B–11.E create/edit, so it follows all of them; its migration is authored after 11.G's so the EF snapshot stays linear. The cold-start items are **11.A**, **11.F**, and **11.G** — kick 11.A + 11.F off first so "play album" works on first ship of the Cut page; 11.G runs alongside on its own track; 11.H waits for the addressing surface to settle.