• v2026.06.20 — Image rendering resilience + player error recovery (skip, self-heal, live refresh)
    test-go / test (push) Successful in 46s
    test-web / test (push) Successful in 55s
    android / Build + lint + test (push) Successful in 4m11s
    test-go / integration (push) Successful in 4m44s
    release / Build signed APK (tag releases only) (push) Successful in 3m53s
    release / Build + push container image (push) Successful in 1m41s
    Stable

    bvandeusen released this 2026-06-20 21:04:04 -04:00 | 26 commits to main since this release

    Per-day CalVer. Cover-art rendering and playback error-recovery hardening across server + web + Android, landed together. Issues #968 (images + player resilience) and #980 (stale-view refresh).

    Covers never sit blank (#968)

    One shared cover renderer per client, so a tile shows a placeholder while loading and a fallback on a 404 instead of an empty box — worst on the You might like row, whose unplayed items often have un-backfilled art.

    • Web: new Cover component owns the loading placeholder + error fallback; AlbumCard / ArtistCard / CompactTrackCard reuse it. ArtistCard previously had no error handler at all.
    • Android: ServerImage now tracks Coil's load state so the per-caller fallback doubles as a placeholder (loading) and an error painter (404), not just a null-URL guard.

    Playback recovers instead of dead-ending (#968)

    • Auto-skip a failed track (web + Android): a track that won't load advances to the next instead of stranding the player on "Try again" (web) / a stalled queue (Android). The error state only appears once the whole queue is unplayable. On Android the "Skipped N unplayable tracks" snackbar now tells the truth — it was firing while nothing actually skipped.
    • Self-heal a stale queue: when the whole queue is unplayable — e.g. a tab/app left open across the daily 03:00 rebuild — the player re-pulls its source (a system playlist via the rotation-aware shuffle, or a radio re-seed) and resumes. Bounded to one re-pull per exhaustion so a still-broken refresh can't loop.

    Browse views refresh themselves (#968)

    The server now emits playlist.system_rebuilt on the daily rebuild and on manual refresh. The web client gained its first inbound SSE listener and Android consumes it via EventsStream — both refresh the Home + playlist views, so a tab open since yesterday stops showing yesterday's mix.

    Stale-view refresh on an open mix (#980)

    A system-playlist detail screen open across the rebuild shows a "This mix was refreshed · Refresh" banner (web) / snackbar (Android). Refresh re-resolves the rotated playlist id to the fresh mix — no forced redirect, no auto-reload, you refresh on your terms.

    Verify

    • Open Home (web or Android) with un-backfilled art → cover tiles show a placeholder then a fallback, never a blank box.
    • Play a queue containing a dead/removed track → it skips to the next instead of dead-ending; only an all-unplayable queue surfaces "Try again".
    • Leave a For You tab open across the 03:00 rebuild → the Home tiles refresh on their own; if you then play the stale queue it re-pulls the fresh mix.
    • Open a system-playlist detail screen and trigger a refresh → a "This mix was refreshed · Refresh" prompt appears; Refresh lands on the new mix.

    🤖 Generated with Claude Code

    Downloads