Genre browse stays route-reachable (deprioritized, not retired). Session/Mix single-track is a hard upload constraint. /albums redirects to /cuts when CUTS lands.
29 KiB
PLAN.md — DeepDrftHome forward roadmap
Forward-looking roadmap. Sits alongside CONTEXT.md (architecture orientation) and COMPLETED.md (history). Per CONTEXT.md §6, items move from here to COMPLETED.md when work lands; do not delete completed entries.
Organised by theme, not by date. Themes are roughly ordered by current product weight, not commitment. Nothing here carries a timeline unless it explicitly says so.
0. Baseline — what just landed
A two-part audit (design + streaming) ran on 2026-05-17 and the fixes for Critical, Major, and Minor findings are now on dev. The remainder of this plan assumes that baseline. In summary the audit-pass fixed:
- Index concurrency —
VaultIndexDirectoryno longer drops the lock before its async disk write; the index file can no longer be clobbered by interleaved writers. - Repository semantics —
TrackRepository.Updatenow fails-fast when anIdis not found instead of silently issuing anINSERT. - Streaming Criticals — concurrent-seek race in the client, dirty trailing bytes leaking out of the
ArrayPool-rented buffer, final-tail audio dropped at EOF below the minimum decode frame, and the assumption that the first network chunk contains the whole WAV header. - 17 design and streaming Majors/Minors across all eight projects — format-validation alignment between processor/offset/decoder,
IAsyncDisposableon the player provider, cancellation tokens threaded through the HTTP path, structured logging into the FileDatabase subsystem, sort-sentinel cleanup, sundry DRY/SRP tightenings.
What this means for the roadmap: the streaming substrate is solid. Future work can build on top of it rather than around it. The remaining items in TODO-V2.md that did not land are deferred as features, not bugs — they are captured below under Phase 1.
Phase 1 — Streaming features deferred from the audit
These were flagged during the audit but classified as feature work, not defect fixes. They are listed in rough order of user-visible impact.
1.3 Preload / prefetch of the next track
- What: No mechanism to begin the next track's stream during the tail of the current. Each play is a cold fetch.
- Why it matters: Prerequisite for both crossfade (1.4) and gapless (1.5). Also a perceived-latency win on its own — track-change feels instant when the bytes are already in flight.
- Shape: A second
HttpClientrequest kicked off when the current track passes a configurable threshold (e.g. last 10 seconds). Bytes accumulate into a stagedStreamDecoderinstance rather than the live one. Promotion to "current" happens at end-of-stream or on user-selected next. - Prerequisite: Requires a notion of "next track" — today the player only knows the current one. That implies either a playlist/queue model in
IPlayerServiceor a passive "what was the next row in the gallery" inference. - Open question: Does a queue model belong in
IPlayerService, or is the player a single-slot device that a futurePlaylistServiceorchestrates above? Worth a design note before implementation. Capture in product notes when picked up.
1.4 Crossfade
- What: Smooth A→B transition with overlapping fade-out / fade-in.
- Why it matters: DJ/mix aesthetic that fits the DeepDrft collective's electronic-music context. Distinguishing UX from generic "next track."
- Shape: Architecturally two simultaneous
PlaybackSchedulerinstances suffice — each owns its own gain node, crossfaded viaGainNode.gain.linearRampToValueAtTime. The wiring is the work, not the audio graph itself. - Prerequisite: 1.3 (Preload) — there is nothing to fade into without prefetch.
1.5 Gapless playback
- What: Eliminate the inter-track silence that exists today.
- Why it matters: Important for live-set rips, mix tapes, anything authored to flow continuously.
- Shape: The decoder must be able to start the next track's first buffer scheduled exactly at the end of the current one's last buffer (sample-accurate, not wall-clock). With
PlaybackScheduler's existing 500 ms lookahead this is mechanically achievable — the next track's firstAudioBufferSourceNode.start(t)is set to the previous track's end time. - Prerequisite: 1.3 (Preload). Also needs to play nicely with 1.2 because gapless across formats is hard (encoder padding/priming on MP3 in particular).
- Constraint: Truly sample-accurate gapless requires knowing the priming/padding sample counts of the source format. Out of scope for WAV-only; revisit when format diversity lands.
1.6 Track-skip on error
- What: A failed
processStreamingChunkaborts the entire load with no recovery path. - Why it matters: One corrupt frame at byte 4M of a 100 MB stream currently means the listener loses the entire track. Should at minimum surface a clear error and (optionally) skip past the bad region.
- Shape: Two-level response.
- Cheap: catch in the streaming loop, surface a user-visible error, advance the gallery to the next track if a queue exists.
- Richer: byte-scan forward to the next valid frame header for the format and resume. Format-dependent — only worth doing once 1.2 lands.
1.7 Safari compatibility
- What: Two known Safari edge cases.
webkitAudioContext.close()is async-but-not-Promise on older Safari (≤ ~14);awaitresolves immediately and the nextinitialize()can run against a not-yet-closed context.- iOS Safari < 15 had streaming-fetch quirks;
HttpCompletionOption.ResponseHeadersReadbehaviour is not guaranteed there.
- Why it matters: Real listener share. iOS in particular is a primary listening surface for music.
- Shape: For the
close()race — detectwebkitAudioContextand pollstate === "closed"with a short timeout instead of trusting theawait. For the fetch quirks — first decide the minimum supported iOS version; if pre-15 is in scope, fall back to a non-streaming fetch path and accept the latency. - Open question: What's the floor? Decide before designing the fallback. iOS 15+ as the floor would let us drop the second concern entirely.
Phase 2 — Product surface: gallery, browsing, ingestion
These follow from CONTEXT.md §5. Direction is strongly implied but no specific UI has been committed.
Phase 6 — CMS Enhancements (Completed)
See COMPLETED.md for Phase 6 (§6.1, §6.3) and entity-prep (§6.2 model layer) which landed on dev in June 2026.
6.2 Card-contextual filtering of the Tracks page — [superseded by §8]
- What: Make the Album and Genre dashboard cards navigate into a filtered
/tracksview (e.g. clicking an album card shows only that album's tracks), rather than the unfiltered table. - Why: Turns the dashboard from a read-only summary into a navigation hub — the natural next step once the cards exist.
- Why deferred: The dashboard cards aggregate across all albums/genres — there is no single album/genre to filter to from a top-level count card. Meaningful per-album/per-genre navigation needs an intermediate browse surface (a list of albums, a list of genres) for the admin to pick from — i.e. it's really a CMS analogue of the public
AlbumsView/GenresView, not a property of the summary cards. That's a larger surface than the dashboard itself and shouldn't be smuggled in. TheGET api/track/pageendpoint already acceptsalbum=andgenre=query filters, so the API substrate is ready; the missing piece is the CMS browse UI and the filter plumbing inTrackList. - Superseded: §8 (CMS Track Browser) builds exactly the intermediate browse surface this item was waiting on — Album Mode and Genre Mode are the CMS analogue of
AlbumsView/GenresView, and the filter plumbing intoGetPagedAsyncis part of §8's data contract. This item folds into §8; do not implement it separately.
Phase 3 — New content kinds
3.1 Live / session content
- What: The home page advertises "Live Sessions" and "Video Content (coming soon)". No data model exists for these.
- Why it matters: Honour the home page copy. Also differentiates the site from a generic track gallery — live sessions and video are the collective's authored output.
- Shape: Speculative; no commitment yet.
- Likely new entity table(s) sibling to
TrackEntity(SessionEntity,VideoEntity?) — or a polymorphicMediaEntitywith discriminator. The choice affects how much code inTrackService/TrackControllercan be reused. - New vault type(s).
MediaVaultType.Mediaexists and is the obvious home for video; sessions are probably stillAudio. - New routes, new UI surfaces, new player considerations (video has its own playback element and does not go through the WAV decoder).
- Likely new entity table(s) sibling to
- Prerequisite: Probably 2.1 (vault wiring proof) and a decision on the entity model before any code lands.
[speculative]— direction inferred from home-page copy, not a Daniel-confirmed commitment.
Phase 4 — Infrastructure / delivery
4.3 Dual-write rollback / dead-letter log
- What: If content-side write succeeds and SQL-side write fails, audio is orphaned in the vault. No compensating mechanism exists.
- Why it matters: A latent data-integrity issue. Materially riskier once web upload (2.4) exists.
- Shape: Audit suggested a
DeadLetterLogrecording orphanedentryKeys for a periodic maintenance pass. Lighter than full transactional rollback (which the dual-database split fundamentally cannot give us). - Prerequisite: None. Worth landing alongside or just before 2.4.
Phase 5 — Documentation backlog
5.1 Folder-level CLAUDE.md sweep
- What: Eight folder-level
CLAUDE.mdfiles need writing/rewriting per the brief inDOC_PLAN.md. Five are rewrites (drift from the.NET 10upgrade and structural moves); three are new (DeepDrftWeb.Services,DeepDrftContent.Services— the two libraries where most domain logic now lives — plus the open question onDeepDrftContent.Services/FileDatabase/README.md). - Why it matters: The agent guidance files are how every future implementer (human or agent) gets oriented in a directory. They are currently misleading in ways that will cause wrong assumptions on first contact — claiming
.NET 9, referencingMediaPaththat has beenEntryKeyfor two migrations, describing aFileDatabase/tree insideDeepDrftContentthat has moved out, and missing entirely for the two*.Serviceslibraries. - Shape: Doc-keeper executes against
DOC_PLAN.md. Order of operations and the per-folder briefs are already specified there. - Prerequisite: None. Can run fully in parallel with any feature work.
- Constraint: Wait on Daniel for the
DeepDrftContent.Services/FileDatabase/README.mdjudgement call before that file changes (retire, keep + refresh, or replace with a CLAUDE.md). The other seven can proceed without that decision.
Phase 7 — Shared UI Components
Reusable presentational components in DeepDrftShared.Client (the RCL consumed by both the public site and the CMS). Distinct from the player stack and CMS surfaces — these are host-agnostic building blocks both apps compose.
Phase 8 — CMS Track Browser
Three browse modes for the CMS /tracks page — Track, Album, Genre — selected by a toggle, each deep-linkable so the public home page can link straight into a mode. One view-model (DI-scoped, matching the TracksViewModel pattern) feeds all three views; the divergence is in rendering, not data paths (per the standing "same data, different uses" preference). This supersedes the deferred §6.2 — Album and Genre modes are the intermediate browse surface that item was waiting on. Full spec: product-notes/phase-8-cms-track-browser.md (normalization gate, component decomposition, VM design, URL scheme, data contracts, open questions).
§8.0 landed on 2026-06-11 — a breaking TrackEntity normalization has been completed and is stable on dev. §8.1–§8.5 are now unblocked. The Waveform Pre-Processing tab is removed, folded into an in-grid status column + per-row/page-level generate actions (see §8.2).
A small set of items that are real but don't fit a phase yet. Surface them when they become relevant rather than committing now.
- Identity / accounts. Currently no user concept. Needed before web upload (2.4); also a precondition for favourites, listening history, per-user playlists. Decide the shape before any of those lands.
[speculative]until Daniel signals interest. ITrackServiceinterface. Audit-suggested. Low value today (one consumer pair); higher value when the test surface expands beyond FileDatabase.- Test coverage outside FileDatabase. Tests today cover the FileDatabase subsystem comprehensively and nothing else. As features in Phases 1–4 land, test scope should expand — at minimum
WavOffsetService,AudioProcessor,TrackService(both sides), and the streaming player services. Not a phase of its own; an attached cost to feature work.
Phase 9 — Release Medium Types
Releases gain a top-level medium discriminator above the existing ReleaseType. Three media: Studio CUTS (Cut — the only medium that uses Single/EP/Album), Live SESSIONS (Session — a single live track with a distinct hero image), DJ MIXES (Mix — a single long track with a preprocessed high-resolution waveform datum). This touches the data model, the API, the CMS, and the public site.
The public home page already carries the three-medium framing as editorial cards (Studio / Live / DJ Mix — COMPLETED.md §8.6, landed 2026-06-12), but those cards have no destinations and nothing below the copy layer knows what a medium is. Phase 9 makes the medium real and gives those cards somewhere to point.
Architectural spine — discriminator enum + optional metadata table. ReleaseMedium is a plain enum column on ReleaseEntity. A medium that needs data beyond the base release (Session's hero image, Mix's waveform datum) gets its own 1:1 metadata table; a medium that needs nothing extra (Cut) is the base ReleaseEntity. This is Open/Closed at the schema level — a future medium (e.g. Video, §3.1) adds an enum value and optionally one metadata table, and changes zero existing tables. The alternatives (one wide nullable table; an EF type hierarchy) both collapse to the god-table the Phase 8 normalization moved away from — rejected. Full design, contracts, and the SOLID rationale: product-notes/phase-9-release-medium-types.md.
Design discipline throughout: extension, not modification. Where a per-medium mapping is unavoidable (card → browser, medium → API projection, medium → detail hero), keep it in one table per concern — never a scattered three-arm switch. Drive CMS cards and nav sub-items off Enum.GetValues<ReleaseMedium>() + a display-metadata lookup, so a new medium surfaces automatically.
The ReleaseType-only-for-Cut invariant. Single/EP/Album is meaningful only when Medium == Cut. Enforce as a domain rule (service layer ignores/resets ReleaseType for non-Cut; CMS hides the field unless Cut), not a DB constraint — EF cannot express a conditional constraint cleanly and a raw CHECK is migration-fragile. The column stays on ReleaseEntity with its default, simply unused for non-Cut media.
Sequenced as four waves. Wave 1 is a prerequisite for everything; within Waves 2–4 the lettered tracks are parallel.
Dependency summary: 1 → 2 → 3 → 4. Wave 4 (public site) can begin once Wave 2's api/release family is stable; it does not wait on Wave 3 (CMS).
9.1 Wave 1 — Data model + migration [prerequisite gate]
- What: New
ReleaseMediumenum (Cut, Session, Mix) inDeepDrftModels/Enums/.ReleaseEntitygainsReleaseMedium Medium(defaultCut) plus 1:1 nav properties to two new metadata entities. NewSessionMetadata(HeroImagePath) andMixMetadata(WaveformEntryKey) entities, each 1:1 withReleaseEntity. EF configurations + migration. - Why: Every other wave reads this schema. The discriminator-plus-optional-table shape is the load-bearing decision of the phase; it must land first and land right.
- Shape:
ReleaseMediumenum withCut = 0(default — existing/migrated releases stay studio cuts with no discriminator data migration).Mediumcolumn onreleases;ReleaseConfigurationdocuments theReleaseType-only-for-Cutinvariant.session_metadataandmix_metadatatables, each with a unique FK toreleases(1:1).MixMetadata.WaveformEntryKeyis a vault entry key (recommended — see open question), not an inline blob.- Migration is additive only — no data migration of existing rows beyond defaulting
Medium = Cut. Lower risk than the Phase 8 normalization.
- Prerequisite: Phase 8 §8.0 normalization (
ReleaseEntityexists) — already landed. - Acceptance criteria:
ReleaseMediumenum exists;ReleaseEntity.Mediumdefaults toCut.SessionMetadata/MixMetadataentities + EF configs + migration applied; solution compiles and existing releases read back asCut.- The invariant is documented in
ReleaseConfiguration(no DB constraint).
- Open questions:
- Waveform storage: vault blob +
WaveformEntryKey(recommended — keeps the high-res datum out of SQL, consistent with the dual-database split) vs. a JSON column onMixMetadata. Determines whether this wave touches the vault abstraction. Resolve before the migration is written.
- Waveform storage: vault blob +
9.2 Wave 2 — API: medium reads + metadata uploads
A new api/release controller — the medium unit is the release, not the track, so medium browse and metadata uploads are release-cardinal rather than bolted onto api/track/page.
- 9.2.A — Release read endpoints (data layer + controller).
- What:
GET api/release?medium={cut|session|mix}&page=&pageSize=&sort=(unauth, paginated, medium filter additive — omitting returns all) andGET api/release/{id}(unauth, single release + its medium metadata). BothIncludethe matching metadata table via a per-medium projection map. - Why: The public CUTS/SESSIONS/MIXES surfaces and the CMS browsers all read releases by medium. One cohesive release-read family keeps
api/track/pagefocused on Phase 8's track-list cases. - Shape: Repository/service join through the metadata tables only for the relevant medium; base release reads never touch them. Projection map is per-medium (extension-shaped), not an
if/elsechain in the controller.ReleaseDtogainsMedium+ optional nestedSessionMetadataDto?/MixMetadataDto?(populated only for the matching medium — mirrors Phase 8's nested-Releasechoice, not denormalized flat fields). - Acceptance criteria:
GET api/release?medium=sessionreturns Session releases with hero-image metadata included and noMixMetadata;medium=cutreturns Cuts with neither metadata block; pagination + sort parity withapi/track/page.
- What:
- 9.2.B — Metadata upload endpoints.
- What:
POST api/release/session/hero-image(ApiKey — hero image → image vault → setSessionMetadata.HeroImagePath) andPOST api/release/mix/waveform(ApiKey — preprocessed waveform datum → vault → setMixMetadata.WaveformEntryKey). - Why: The CMS authoring flows (Wave 3 B/C) need write paths for the medium-specific data. Splitting them from the track-upload endpoint keeps each endpoint single-responsibility.
- Shape: Hero-image upload mirrors the existing cover-art
UploadImageAsync→ image-vault → link pattern, targetingHeroImagePath. Waveform upload writes the datum to the vault and records its entry key. Both find-or-create the metadata row for the release. - Acceptance criteria: Posting a hero image to a Session release sets
HeroImagePathand the image is served back through the existing image proxy; posting a waveform datum to a Mix release setsWaveformEntryKeyand the datum is retrievable.
- What:
- Prerequisite: 9.1.
- Open questions:
- New endpoints vs.
api/track/pagequery-param extension. Recommend the newapi/releasefamily (release-cardinal browse, medium metadataInclude);api/track/pagecan gain a cheapmedium=passthrough later if a track-level filter is ever needed.
- New endpoints vs.
9.3 Wave 3 — CMS: Release Archive tab, medium selector, medium browsers
- 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 perReleaseMedium, styled like the existingCmsGenreBrowsercards) where each card navigates to a medium-specific browser. Add aReleaseMediumselector toTrackNew/TrackEdit/BatchUpload/BatchEdit/AlbumHeaderFields; showReleaseTypeonly whenMedium == 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 aMediumFilter); Session card →CmsSessionBrowser; Mix card →CmsMixBrowser. Selector-driven conditional fields may be explicit@ifper medium in the form (three genuinely different field sets — SOLID discipline matters at the data/service layer, not the form markup; do not over-abstract the UI). - 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
ReleaseTypeonly forCut.
- What: Rename
- 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
CmsAlbumBrowseris the wrong shape for them. - Shape: Reuse
CmsTrackGridparameterized byMediumFilterwhere 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 againstHeroImagePath. - Acceptance criteria: Session browser lists only Session releases; uploading a hero image persists it and renders the thumbnail.
- What: New
- 9.3.C —
CmsMixBrowser+ waveform pipeline.- What: New
CmsMixBrowser.razor— a flat list of Mix releases (Medium == Mix) with an in-grid waveform-generation status column (mirroring Phase 8'sHasWaveformProfileidiom) and a per-row Generate Waveform action. Wire the Mix upload to trigger high-resolution waveform preprocessing and upload the datum (9.2.B). - 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.
- Shape: Model the high-res preprocessor on the existing player-bar waveform preprocessor — the same pipeline parameterized by resolution, not a copy (player-bar peek = low-res, Mix = high-res; honours One source, multiple views). Upload flow:
UploadTrackAsync→ preprocess →POST api/release/mix/waveform. - Acceptance criteria: Mix browser lists only Mix releases and shows per-row waveform status; uploading a Mix generates + stores a high-res waveform; the per-row Generate action recovers a missing waveform.
- What: New
- 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. Is the existing preprocessor factored to accept a resolution parameter, or does 9.3.C include a refactor to share one pipeline across player-bar and Mix? Recommend one parameterized pipeline.
- 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.
9.4 Wave 4 — Public site: ARCHIVE nav, CUTS / SESSIONS / MIXES, waveform visualizer
- 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.razorrendersPages.MenuPagesas a flat<a>list today with no dropdown mechanism. Recommend extending the nav model with an optionalChildrencollection (generalizes to future dropdowns; mobile renders children as indented sub-links) over a bespoke hardcoded popover. - Acceptance criteria: ARCHIVE replaces the three flat items; desktop hover reveals the three sub-links; mobile routes to
/archive; no dead links remain.
- What: Replace the current RELEASES / SESSIONS / MIXES nav items (in
- 9.4.B — CUTS (
/cuts).- What: New
/cutsroute reusing the existingAlbumsViewlayout, filtered toMedium == 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=AlbumsViewwithMedium == Cut. - Acceptance criteria:
/cutsshows onlyCutreleases with the current AlbumsView ergonomics. - Resolved: When
/cutslands, the existing/albumsroute issues a redirect to/cuts. Old URLs keep working; no hard 404.
- What: New
- 9.4.C — SESSIONS (
/sessions+/sessions/{id}).- What: Gallery of session cards (cover, session name, artist) at
/sessions; detail at/sessions/{id}mirroringTrackDetailbut 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 sharedReleaseDetailScaffold(extracted common metadata + play + player wiring) with a hero-image hero slot — see 9.4.D open question. - Acceptance criteria:
/sessionslists Session releases;/sessions/{id}renders hero-dominant with the play affordance intact.
- What: Gallery of session cards (cover, session name, artist) at
- 9.4.D — MIXES (
/mixes+/mixes/{id}) +MixWaveformVisualizer.- What: Gallery at
/mixes; detail at/mixes/{id}whose above-the-fold hero is aMixWaveformVisualizercomponent fed by the preprocessed waveform datum fromMixMetadata. 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:
MixWaveformVisualizertakes the waveform datum (viaWaveformEntryKey→ content endpoint) + optional playback-position binding; renders SVG/canvas peak-bars in the establishedSpectrumVisualizer/LevelMeterFabvisual language (don't invent a new idiom). Detail composes the sameReleaseDetailScaffoldwith the visualizer as its hero slot. - Acceptance criteria:
/mixeslists Mix releases;/mixes/{id}renders the waveform visualizer fed by real datum; the visualizer is a standalone reusable component. - 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).
- What: Gallery at
- Prerequisite: 9.2 (the
api/releaseread family). Independent of Wave 3. - Open questions:
- Detail-page strategy. Three separate detail pages vs. one branching
TrackDetailvs. a sharedReleaseDetailScaffold+ per-medium hero slot. Recommend the scaffold (DRY-by-composition, the Phase 8BatchUpload/BatchEditextraction move; honours One source, multiple views). Sets the shape of 9.4.C and 9.4.D.
- Detail-page strategy. Three separate detail pages vs. one branching
Working with this file
- Add items by extending an existing phase first; only create a new phase when the addition genuinely doesn't fit any of 1–5. Phase numbers are organisational, not sequencing.
- When something lands, move it to
COMPLETED.mdrather than deleting it. Keep the original "What / Why / Shape" body intact so the history reads as a record of the decision, not just the outcome. - Mark genuinely uncertain items
[speculative]so future readers can tell what is direction vs. commitment. - Open questions belong in the item that raises them, not in a separate "questions" list — they expire when the item does.