-
released this
2026-05-13 22:37:08 -04:00 | 1159 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 | 1205 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 | 1214 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 | 1231 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 | 1234 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
-
released this
2026-05-11 11:22:10 -04:00 | 1240 commits to main since this releaseRelease containing M5a Lidarr, M5b quarantine, M6a recommendations, M7 web frontend, Flutter mobile client v1, and the recent Flutter polish session. See PR #38 for the full breakdown.
Operator notes:
- Pull the new server container image and roll prod
- Sideload minstrel-v2026.05.11.0.apk from this release page when CI finishes attaching it
Downloads
-
v2026.05.10.2 Stable
released this
2026-05-10 23:20:59 -04:00 | 1269 commits to main since this releaseTwo follow-ups to v2026.05.10.1:
- Download button now appears in Settings → Mobile app section. The component was reading camelCase (
apkUrl/sizeBytes) but the server emits snake_case (apk_url/size_bytes); fixed by mapping at the wire boundary. - Server version visible in the UI: small "Server v2026.05.10.2" line at the bottom of Settings, sourced from
/healthz(now includes theversionfield stamped at build time viaMINSTREL_VERSIONbuild-arg → ldflag).
Verification after deploy
- Download button: Settings → scroll to Mobile app section → "Get the Android app · v2026.05.10.2 · ~62 MB" link should be visible.
- Server version: bottom of Settings page → "Server v2026.05.10.2" subtle text.
- Healthz includes version:
curl https://<host>/healthz→ JSON should include"version": "v2026.05.10.2". - In-app update on existing install: any older Flutter client should detect this release on its next 24h poll → banner appears → install via system dialog.
See PR #37 for the full diff.
Downloads
- Download button now appears in Settings → Mobile app section. The component was reading camelCase (
-
v2026.05.10.1 Stable
released this
2026-05-10 21:45:22 -04:00 | 1274 commits to main since this releaseRe-release of v2026.05.10.0's content with the CI token consolidation fix (PR #36) so the bundled-APK update channel actually ships.
Same content as v2026.05.10.0 (see PR #35 for the full change list); the only delta is the CI workflows now use the unified
CI_TOKENsecret, which letsflutter.ymlattach the APK to the release andrelease.ymlbundle it into the image.What to verify after CI
flutter.yml"Attach release APK to release" step succeeds — APK shows up on this release page asminstrel-v2026.05.10.1.apkrelease.yml"Fetch APK for bundled in-app update channel" step succeeds within 15 min- Deployed image
:v2026.05.10.1has/app/client/minstrel.apkpopulated (docker exec ... ls /app/client/) curl -H 'Cookie: <session>' /api/client/versionreturns the version JSON
Test plan (carried from PR #35)
- Offline browse — first sync after upgrade fully populates drift; library/artist/album surfaces should show real names instead of empty rows.
- In-app update — existing client should detect this release on next 24h poll → banner appears → install via system dialog.
- add-to-playlist sheet — system playlists (For You / Discover) should be hidden.
- Mobile responsive — walk through at 375 / 414 / 768 px.
- Stream resilience — toggle airplane mode briefly during playback; auto-retry should kick in.
Downloads
-
v2026.05.10.0 Stable
released this
2026-05-10 21:10:04 -04:00 | 1276 commits to main since this releaseFirst test release of the M5–M7 sweep + offline cache + in-app updates + DRY pass batch.
See PR #35 for the full change list. Headline themes:
- Flutter offline mode (#357) — drift-backed metadata cache + LockCachingAudioSource + delta sync. Includes a wire-format fix that makes offline browse actually work end-to-end (prior images had broken sync JSON keys).
- Flutter parity (#356) — search, discover, queue, library 5-tab, settings, playlists, admin, history, requests view + cancel. systemVariant column closes the v1 limitation in plan C.
- In-app update flow (#397) — first release with the bundled-APK update channel. Server endpoints
/api/client/version+/api/client/apk(auth-gated, rate-limited). Flutter banner + AndroidPackageInstallerintent. CI bundles the matching APK into this image at/app/client/. - Web mobile responsive (#358) — hamburger nav, 2-row PlayerBar at <md, RowActionsMenu for admin tables.
- Web stream resilience — preload=auto, stall recovery, next-track prefetch with audioLoader seam (Tauri-ready).
- Web DRY pass (#375) — test-utils helpers, playlists.ts → api.* migration, auth↔player cycle inversion, CardActionCluster.
Test focus
- Offline browse — first sync after upgrade fully populates drift; library/artist/album surfaces should show real names instead of empty rows.
- In-app update — existing client should detect this release on next 24h poll → banner appears → install via system dialog.
- add-to-playlist sheet — system playlists (For You / Discover) should be hidden.
- Mobile responsive — walk through at 375 / 414 / 768 px.
- Stream resilience — toggle airplane mode briefly during playback; auto-retry should kick in.
Downloads
-
v2026.05.08.4 Stable
released this
2026-05-08 22:20:03 -04:00 | 1388 commits to main since this releaseRemoves the bootstrap-admin path. Self-registration is now the only way to create the first admin on a fresh deploy.
What changed
internal/auth/bootstrap.goand its test — goneauth.Bootstrap()startup call removed fromcmd/minstrel/main.goAuthConfig+AdminBootstrapConfigstructs removed frominternal/configMINSTREL_AUTH_ADMIN_USERNAME/MINSTREL_AUTH_ADMIN_PASSWORDenv vars no longer readauth:block removed fromconfig.example.yaml- Bootstrap-related comments removed from
docker-compose.yml - README quickstart now points at
/registerinstead of "watch logs for password"
Net diff: +1 / -303 across 8 files.
New first-run flow
docker compose up- Visit
http://<host>/register - First user on an empty
userstable becomes admin automatically (existingCreateUserFirstAdminRacepath — now the only path)
Operator notes
- Existing instances that already have a bootstrap admin in the DB: the row sticks around (no migration), and the admin still works. To get rid of it, register a new admin via
/register(will need an invite token, sinceusersis non-empty), promote them in/admin/users, then delete the bootstrap account. - Fresh instances (including the prod instance reset for this release): just visit
/register. - Default registration mode stays
invite_onlyafter user #1 — generate invite tokens from/admin/usersfor additional users. UI for switching toopenregistration is a small follow-up task. - Recovery story: forgotten admin password on a single-admin instance now requires Fable #321 (CLI password reset, not yet shipped). Worth scheduling soon. The bootstrap path didn't help here either — it only fired on empty
users.
Downloads