• v2026.06.02
    android / Build + lint + test (push) Successful in 5m36s
    release / Build signed APK (tag releases only) (push) Successful in 4m28s
    release / Build + push container image (push) Successful in 2m1s
    Stable

    bvandeusen released this 2026-06-02 22:30:20 -04:00 | 239 commits to main since this release

    Same-day re-cut on the per-day mutable CalVer scheme. This re-cut is a hotfix for the v2026.06.02 (run #289) connectivity regression — fresh installs of the prior cut couldn't sign in and were stuck at the Welcome screen.

    Hotfix: Android interceptor order (PR #76)

    NetworkModule.kt chained BaseUrlInterceptor before AuthCookieInterceptor. Both interceptors scope work on host == PLACEHOLDER_HOST (drift #568/#569, to keep the session cookie off Coil image fetches to MusicBrainz / coverartarchive / Lidarr). With BaseUrl first, the URL was rewritten to the real Minstrel host before auth ran — so auth's placeholder check failed on every request, the session cookie was never attached on outgoing requests, and Set-Cookie from login was never captured. The AuthCookieInterceptorTest chains the interceptors in the correct order, which is why the regression went undetected. Fix: swap to (auth, baseUrl, logging). Client-side only — server is unchanged.

    Anyone who installed the prior v2026.06.02 APK needs to re-install from this release.

    Highlight: drift audit shipped (26 findings)

    Multi-system audit run on 2026-06-02: 14 auditors (12 subsystem + 2 cross-cutting) fanned out across server / Android / web / Flutter looking for the 10-pattern drift taxonomy. 26 candidate findings surfaced; 24 confirmed by adversarial verify and shipped, 1 cancelled as duplicate, 1 doc-only correction. Scribe parent task: project 12 / task 552.

    Highest-impact fixes

    • Server /api/me profile shape (#578). The endpoint emitted UserView (id/username/is_admin) but Android's MyProfileWire expected display_name + email. Every Settings → Profile open silently saw nulls; saving from that blank state DESTROYED stored profile fields via the "empty string clears to NULL" path. Server now returns the full profile shape. Existing test extended; new regression-guard test added.
    • AuthCookieInterceptor scoping (#568 #569). The Minstrel session cookie was being attached to every external request that the shared OkHttpClient services (Coil image fetches to artwork.musicbrainz.org / coverartarchive.org / Lidarr /MediaCover URLs), and a 401 from any external host wiped the user's session. Both attach + clear now scoped to the placeholder.invalid sentinel host — mirrors the BaseUrlInterceptor fix from earlier today.
    • System-playlist rotation alignment (#563 #564). Server's rotation whitelist had for_you + discover only but migrations 0021 + 0028 added 6 more variants; plays from Rediscover / Deep Cuts / Songs Like X / etc. didn't advance the per-user rotation. Android also emitted source="playlist:<variant>" while the server matched the bare variant, so system-mix plays from Android Home never registered at all. Whitelist extended; Android source format aligned with web's bare-variant contract.
    • Cold-boot resume coordination (#560 #562). ResumeController could race the MediaController IPC handshake — setQueue early-returned on null controller and the persisted queue was silently dropped. ResumeController now awaits a new awaitReady() suspend before calling setQueue, and setQueue gains an autoplay opt-out so cold-boot resume restores paused (not auto-playing).
    • Cross-device unlike on Android (#570). LikesRepository.refreshIds only INSERTed — rows the server no longer surfaced (because the user unliked elsewhere) stayed in the local cache forever. Atomic replaceAllForUser DAO transaction now wipes-then-inserts so the local set is exactly what the server reports.
    • CONFLATED playback-error channel (#561). The reporter's burst-coalescing path was dead code — Channel.CONFLATED silently dropped every emission except the latest. A network blip that failed 5 tracks surfaced only the last; the admin inbox only got one POST. Now Channel.BUFFERED.
    • Periodic GC worker (#565 #566 #567 #574 #575). New internal/gc package — one Worker, 5 idempotent sweeps on a 1h tick: close stale play_events (24h), close idle play_sessions (6h) / empty-starved (1h), expire scrobble_queue failed rows (14 days), reset stuck system_playlist_runs (10 min), delete expired password_resets. Closes the lifecycle-gap cluster from the audit in one shot.
    • Profile + lifecycle UX (#553 #554 #555 #557 #558 #559 #571 #572 #576 #577). Tailwind class fix on the playback-errors Delete confirm button; play_started source field added to the EventRequest TS type; coverage rollup test extended to deezer/lastfm; /forgot-password + /reset-password/[token] reachable without auth; SSR-only redirects converted to client-side; scanner indexes .opus/.aac/.wav; delete.go docstring honest about missing reconcile; LikesRepository uses real user UUID + clears on user-switch; RequestsViewModel doesn't refresh() on offline-queued cancel.

    Verify on device

    • Fresh APK install: sign in succeeds and lands on Home (was stuck at Welcome).
    • Android Settings → Profile shows stored display_name + email (was wiping them).
    • Drag-dismiss + cold-boot of NowPlaying: pop animation latches, resume opens paused (not playing).
    • /admin/playback-errors → Delete file modal: confirm button is the proper Oxblood destructive color.
    • Tap Cancel on a request while offline — row stays gone, no Error screen.
    • Discover view shows artist artwork (regression for prior fix).
    • New scrubber thumb reads as a small flat circle on a thin 4dp track.

    Carried from prior cuts

    Earlier same-day cuts already included: playback-errors slice (server + Android + admin UI), drag-dismiss latch, portrait lock, Material LibraryMusic icon (later reverted to Lucide.LibraryBig), notification tap → NowPlaying, NowPlaying polish (dominant swatch, slim scrubber, swipe tabs, refresh flicker fix, Start Radio preserves playback), web alphabet rail page-chasing, Songs Like fix, two-pane desktop NowPlaying, scrubber smoothing, CalVer-with-iteration APK versionName.

    Downloads