-
v2026.05.15.0 Stable
released this
2026-05-15 23:16:08 -04:00 | 687 commits to main since this releaseSystem playlists v2, the offline-cache rework, and a major CI speedup.
System playlists v2 (#411)
- For-You no longer rebuilds on play; shuffle-on-play default; For-You expanded to 100 tracks; server-side per-rotation sample-history dedup so re-plays within a day vary.
- Generalized refresh affordance (kebab/Regenerate) across web + Flutter via a kind registry — generic
/api/playlists/system/{kind}/{refresh,shuffle}. - Five new mixes: Deep Cuts, Rediscover, New for you, On This Day, First Listens.
- "Refreshed …" freshness subtitle on system tiles.
- Play-event correctness: naturally-finished in-queue tracks no longer mis-recorded as skips (was degrading recommendations), plus offline play replay so mobile/cached listening actually counts.
Offline cache rework (#427)
- Reachability-based offline mode (
/healthzprobe). - Two independent cache budgets — Liked and Recently-played, 5 GB each, tunable — replacing the single cap; liked tracks are protected from rolling-cache pressure.
- Offline: system playlists gate off; an always-present Shuffle all plays the cached union; Home surfaces "Recently played" / "Liked" pools. Online, Shuffle all draws from the whole library.
CI
- Flutter dev runs drop from ~10 min to ~3 (debug APK now main-only); the runner image bakes the Android SDK so the ~7-min per-run download is gone; release/main builds materially faster.
Mobile client version 2026.5.15+6. Post-release device verification focuses on the two-bucket eviction (Settings) and the airplane-mode flows.
Downloads
-
v2026.05.14.0 Stable
released this
2026-05-14 21:17:57 -04:00 | 715 commits to main since this releasePlayer feel, background metadata sweeper, and the first offline-resilience layer for outbound user actions.
Player
- Track-change no longer leaks the previous track's art/metadata mid-swap (
setQueueFromTracksnow sets source before broadcasting;_scheduleSwaphas a fast sync path on warm cache). - Prefetcher pre-warms cover bytes + palette for the next N tracks so the swap is instant.
- Playback stream errors auto-skip instead of stranding the UI on a broken track.
- Reverted the Wear MediaSession surface expansion (Pixel Watch 2 regressed when we advertised setRating); LikeBridge stays wired but dormant.
CacheFiller
Background sweeper that walks the drift cache for artists missing albums and albums missing tracks, fills via
/api/artists/:id+/api/albums/:id, and pre-warms covers. 10s initial, 5-min interval, 200ms throttle.Offline mutation queue
New
cached_mutationstable +MutationReplayer. Likes, hide/unhide, add-to-playlist, Lidarr request, request cancel all now persist the user's intent across network loss: optimistic local write, REST attempt, queued for replay on failure. 3s initial drain, 1-minute periodic ticker, 5-attempt drop.Fixes
- Liked tab no longer spins forever on a fresh install (
cacheFirstcold-fetch loop). - Library Artists grid: artist circles stay round at 3-column widths.
- Restored artist coverUrl on drift-cached
ArtistRef(LEFT JOIN cached_albums). - Discover system playlist no longer duplicates tracks across buckets.
Downloads
- Track-change no longer leaks the previous track's art/metadata mid-swap (
-
released this
2026-05-14 14:19:53 -04:00 | 728 commits to main since this releasePatch batch on top of v2026.05.13.2. See PR #46 for the full breakdown.
Fixes
- Full player showed "Nothing playing." while the mini bar showed a live track. Regression from the previous release's load-then-swap rewrite: ref.listen doesn't fire on initial subscription, so opening the full player while a track was already playing left the displayed state null. initState now seeds it synchronously.
Features
-
External media controllers (Android Wear, Auto, Bluetooth dashes, lock-screen widgets) now see a complete MediaSession surface. Several handler methods were already implemented but never advertised in PlaybackState.systemActions; Android 13+ silently drops any callback for an action not in that set.
- controls list adds MediaControl.stop
- systemActions adds stop, skipToQueueItem, setShuffleMode, setRepeatMode, setRating
- stop() override actually halts the player + dismisses the notification (default was a no-op)
-
Native heart-rating wired through to LikesController. The setRating action is the standard MediaSession protocol for favorite-style buttons on Wear, lock-screen widgets, and Auto. New LikeBridge passes through configure(); _toMediaItem populates MediaItem.rating from likedIdsProvider so the right filled/outlined heart shows on every track change; PlayerActions listens to likedIdsProvider and calls refreshCurrentRating so toggles from TrackRow / kebab / another device (SSE) also reflect on the watch without waiting for the next track.
-
MediaItem.artUri populated on the first broadcast for warm-cache tracks. New AlbumCoverCache.peekCached returns the file path synchronously when the cover is already on disk; external controllers see the cover immediately instead of waiting for the async _loadArtForCurrentItem path.
Operator notes
- No schema changes.
- In-app updater should upgrade cleanly (versionCode 3 → 4).
- Sideload minstrel-v2026.05.13.3.apk from this release page when CI finishes attaching it.
Downloads
-
released this
2026-05-14 12:31:56 -04:00 | 733 commits to main since this releasePatch batch on top of v2026.05.13.1. See PR #45 for the full breakdown.
Fixes
-
Artist tiles rendered the music-notes placeholder everywhere except the artist detail screen. CachedArtistAdapter.toRef() dropped coverUrl; cached_artists doesn't store the representative album id the server derives at query time. Queries used by artistTileProvider / libraryArtistsProvider / artistProvider now LEFT JOIN cached_albums ordered by sort_title; toRef reconstructs
/api/albums/<id>/coverfrom the first joined album. Fixes home Rediscover Artists, Library Artists tab, and Liked Artists carousel. -
Mini player cover flickered on track change. Drop AnimatedSwitcher; PlayerBar is now stateful and holds the most-recent non-null artUri. Previous cover stays visible across the audio_handler's bare-then-with-artUri rebroadcast pair, new cover snaps in the moment its artUri arrives. No transition, no placeholder flash.
-
Full player track-change still snapped art + backdrop in. Rewrite around "load first, then swap": _displayedMedia + _displayedDominant state, ref.listen schedules a preload that awaits precacheImage + albumColorProvider.future, only then setState atomically commits both. Cover, title, gradient now advance in one frame. Backdrop AnimatedContainer still tweens between successive committed colors so the gradient transition remains smooth.
Operator notes
- No schema changes.
- In-app updater should upgrade cleanly (versionCode 2 → 3).
- Sideload minstrel-v2026.05.13.2.apk from this release page when CI finishes attaching it.
Downloads
-
-
released this
2026-05-14 11:18:05 -04:00 | 737 commits to main since this releasePatch batch on top of v2026.05.13.0. See PR #44 for the full breakdown.
Highlights
Player fixes
- Queue button on the now-playing screen no longer no-ops (missing skipToQueueItem override; now rebuilds the source list via setQueueFromTracks)
- Tapping a new track in a playlist immediately silences the previous one (pause() before async _buildAudioSource so old audio doesn't bleed through)
- Mini player cover crossfades through track changes instead of flashing slate placeholder
- Full-player track-change animation: precacheImage on the new file:// artUri + held previous dominant color keep the gradient + album art from snapping in mid-transition
Discover playlist
- Adjacent duplicate tracks fixed. interleaveBuckets now dedups across buckets — single-user servers were hit hardest because empty crossUser slots redistributed to dormant + random and shared tracks landed at the same interleaved position. Existing duplicates clear at the next 03:00-local playlist refresh.
Library + UI
- Artist circles in the Library 3-column grid stay perfectly round at any cell width (ArtistCard now mirrors AlbumCard's width-parameter pattern)
- Library Artists + Albums tabs are now drift-first. Reads from cached_artists/cached_albums populated by sync; cacheFirst handles the cold-install fallback. Tab swipes are instant from the second visit forward; loadMore + infinite scroll dropped (drift-all-at-once + GridView.builder lazy rendering handles typical libraries fine).
- systemPlaylistsStatusProvider drift-first via new CachedSystemPlaylistsStatus single-row table (schema 7). Home Playlists row paints with prior status instead of flickering through
emptyduring cold REST. - Library tab pre-warm: ref.listen on all 5 tab providers in LibraryScreen.build subscribes them upfront so the first cold visit pays the network cost in parallel rather than per-tab on swipe.
Cleanup
- 404 log noise from missing playlist covers (collages not yet built) silenced in dev console; auth/connectivity errors still surface.
Operator notes
- Drift schema bump 6 → 7 (CachedSystemPlaylistsStatus added). Migration runs in-place on first launch.
- Pull the new server container image — Discover dedup affects new playlist builds; existing duplicates in drift clear at the next 03:00-local refresh.
- In-app updater should pick this up cleanly (versionCode bumped 1 → 2).
- Sideload minstrel-v2026.05.13.1.apk from this release page when CI finishes attaching it.
Downloads
-
released this
2026-05-13 22:37:08 -04:00 | 747 commits to main since this releaseSubstantial batch since v2026.05.12.1 — 45 commits. See PR #43 for the full breakdown.
Highlights
- Live updates (#392, #402) — SSE event bus at /api/events/stream; Flutter consumer with screen-scoped invalidation for likes, quarantine, playlist mutations, request status, scan lifecycle
- Per-user playlist scheduling (#403) — system playlists refresh at 03:00 in each user's local timezone via CRON_TZ-prefixed gocron jobs; PUT /api/me/timezone + flutter_timezone capture
- Discover playlist (#400) — fixed (SELECT DISTINCT + ORDER BY plan error); daily seed rotation + jitter + 12/13 head/tail split so the same library produces noticeably different daily mixes
- Non-blocking version check — VersionGate no longer blocks app render on /healthz; 1-minute active-use polling with bounded 3s/2s timeouts; banner surfaces when client is too old without stalling the app
- Offline cache completion (#357) — drift-first homeProvider, library_changes retention compactor (30-day window), drift-first Liked / History / MyQuarantine tabs
- Cover caching — cached_network_image disk cache for every cover URL (survives scroll-off + app restart); SyncController prewarms covers for new/updated entities at concurrency=3
- Per-item rendering — new /api/home/index endpoint returns IDs only; HydrationQueue (concurrency=4) drives per-tile fetches against /api/albums/:id, /api/artists/:id, /api/tracks/:id; skeleton tiles match real-card dimensions; 220ms cross-fade reveal
- Flutter UI polish — #393 always-visible play buttons on home carousels, #396 full-screen now-playing polish (gradient backdrop + Hero animation + crossfade), #401 now-playing row highlight
Cold-visit behavior: shaped page paints instantly with skeletons, content cascades in as the hydration queue drains. Warm visits paint fully from drift in the first frame.
Deferred
- Full per-track playlist detail (Fable #405) — skeleton-row variant captures most of the perceptual win; full version awaits if cold visits to very large playlists still feel sluggish
- CachedHomeSnapshot cleanup (Fable #406) — legacy table + homeProvider left in place as revert path
Operator notes
- Drift schema bumps 2 → 6 (4 new tables). Migrations are in-place.
- New users.timezone column on the server (migration 0026)
- In-app updater should upgrade cleanly from v2026.05.12.0
- Pull the new server container image and roll prod
- Sideload minstrel-v2026.05.13.0.apk from this release page when CI finishes attaching it
Downloads
-
released this
2026-05-11 23:58:58 -04:00 | 793 commits to main since this releasePolish + fix batch since v2026.05.12.0. See PR #42.
Highlights:
- Discover system playlist now appears as a tile on both home pages (Flutter + web)
- Full-player kebab "Go to album/artist" no longer crashes
- Artist detail album grids re-fetch instead of rendering partial caches forever
- Diagnostic-print cleanup pass — log noise dropped, errors-only retained
In-app updater should upgrade you cleanly from v2026.05.12.0.
Downloads
-
released this
2026-05-11 20:36:13 -04:00 | 802 commits to main since this releaseSubstantial batch since v2026.05.11.2. See PR #41 for the full breakdown.
Server perf wins on three hot endpoints:
- /api/albums/{id} — 3 round trips to 2
- /api/artists/{id} — 1+N round trips to 2 (32 collapsed to 2 for a 30-album artist)
- /api/home — aggregate-first rewrites of the two scan-the-world recommendation queries
Flutter caching reset: stale playlist reconcile, 404 recovery, full track row writes from playlist fetches, stream-cached audio files now register in the index (eviction works), conservative metadata prefetcher (artists only, one-shot per id), dropped redundant SWR refresh on album/artist providers.
Flutter player: /queue duplicate-page-key crash fixed, full-player layout consolidated (shuffle/repeat/queue + like + kebab in one row), error/state diagnostics in place to catch silent stops.
Flutter UX: playlist cover art now persists across the drift round-trip; 1px album-card overflow silenced.
Operator notes: this APK is the second release signed with the persistent keystore, so the in-app updater should upgrade cleanly from v2026.05.11.2 — no sideload needed unless something goes wrong.
Downloads
-
released this
2026-05-11 15:51:52 -04:00 | 819 commits to main since this releaseTwo changes since v2026.05.11.1. See PR #40.
- Persistent release signing key via CI secret. This is the first release signed with the operator's keystore, so existing installs (debug-keyed from prior releases) must be uninstalled before sideloading this APK. All future tagged builds will upgrade cleanly via the in-app updater.
- Library Artists + Albums tabs now paginate via infinite scroll.
Operator notes:
- Uninstall current build on the test phone before sideloading minstrel-v2026.05.11.2.apk
- Verify the APK installs cleanly past the scanner this time
- Confirm signing-key secrets (ANDROID_KEYSTORE_B64, ANDROID_KEY_ALIAS, ANDROID_STORE_PASSWORD, ANDROID_KEY_PASSWORD) are in place — CI will hard-fail without them
Downloads
-
released this
2026-05-11 13:47:55 -04:00 | 822 commits to main since this releaseFlutter-only follow-up to v2026.05.11.0. See PR #39 for the full breakdown.
Highlights:
- SWR everywhere + navigation hydration + cold-start home skeleton (no more 30s blank)
- Library Artists tab unblocked (was 404), Albums tab in 3-up grid
- System playlist tap now loads tracks
- Player title/artist/cover update on skip
- Player kebab "Go to artist" works
- New "Start radio" menu item
Operator notes:
- Pull the new server container image (no server changes this release, but tag triggers a rebuild)
- Sideload minstrel-v2026.05.11.1.apk from this release page when CI finishes attaching it
Downloads