Replace path now updates SQL DurationSeconds via unconditional SetDuration; document SetDuration vs null-guarded UpdateDuration and correct the stale 'SQL is not written' note.
UpdateDuration's null guard matched zero rows for tracks that already had a duration (all normally-uploaded tracks). Add SetDurationAsync/SetDuration/ITrackService.SetDuration with no null guard; fail on zero rows. ReplaceAudioAsync now calls SetDuration.
New nullable Description column (max 4000) on ReleaseEntity, rides the Genre write channel through upload + edit; multiline CMS input. Migration authored, not applied.
Retire the three-card overview for a search + medium + genre browser over all
releases. Adds q/genre filter params to the api/release paged read path,
mirroring the existing api/track/page TrackFilter pattern.
Promote the Session/Mix single-track rule from a CMS-form convention to a
domain invariant: declare cardinality as data in MediumRules, enforce it in
UnifiedTrackService before the vault write (no orphan), return 409, and read
the same rule in the batch-form collapse.
Adds ReleaseRepository/ReleaseManager (IReleaseService) for paged medium-filtered
release reads and Session/Mix satellite writes, UnifiedReleaseService orchestrating
vault+SQL, and ReleaseController (5 endpoints). Refactors WaveformProfileService for
configurable bucketCount/vaultName (backward-compatible) and adds the mix-waveforms vault.
Promotes brittle error-string literals to named constants (MixHasNoTrackMessage,
MixTrackNoAudioMessage) on UnifiedReleaseService.
Add ReleaseMedium enum (Cut/Session/Mix) and two 1:1 satellite entities
(SessionMetadata, MixMetadata) with EF configs and an additive migration.
ReleaseDto.ReleaseType is now nullable, nulled for non-Cut at the converter.
Existing releases default to Cut via column default; no data migration.
- Add CmsTrackBrowserViewModel.Invalidate(); called from TrackEdit/BatchEdit on save or delete so album/genre cache is invalidated and re-fetches on next mode switch
- CmsAlbumBrowser now handles 0-track releases: confirm dialog + DeleteReleaseAsync instead of early return; partial-failure path also fires OnReleasesChanged to trigger cache invalidation
- TrackList.OnAlbumsChanged now calls VM.Invalidate() so genres stay fresh after any album delete
- UnifiedTrackService.DeleteAsync cascades release soft-delete when last live track is removed (non-fatal; logs on failure)
- New DELETE api/track/release/{id} endpoint (ApiKeyAuthorize) for direct release soft-delete
- EF migration SoftDeleteOrphanedReleases backfills existing orphaned release rows via raw SQL (data-only, no schema change)