-
released this
2026-05-11 11:22:10 -04:00 | 828 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 | 857 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 | 862 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 | 864 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 | 976 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
-
v2026.05.08.3 Stable
released this
2026-05-08 22:08:27 -04:00 | 978 commits to main since this releaseFlutter admin parity slice — closes the operator-actionable subset of the admin surface on mobile and simplifies the home AppBar.
Flutter (admin slice)
Three new fullscreen routes under
/admin:- Requests — list of pending Lidarr requests with Approve / Reject. Joins requester
user_id→ username client-side viaadminUsersProvider. - Quarantine —
ExpansionTileper aggregated track row (collapsed: track + report-count + top reason; expanded: per-user reports). Three-dot menu with Resolve / Delete file / Delete via Lidarr; both deletes route through aTypedConfirmSheetrequiring typed "DELETE". - Users — Users + Invites in one scroll. User edit sheet exposes admin / auto-approve toggles, admin-supplied password reset, typed-confirm delete. Invites section shows token (monospace, copy button), optional note, redeemed badge; "Generate" opens a note-input dialog (server hardcodes 24h TTL).
Plus an
AdminLandingScreenwith three section cards driven byadminCountsProvider(parallel fan-out for at-a-glance counts).Flutter (nav restructure)
Home AppBar simplified from 5 icons to 3 primary + a kebab overflow:
- Primary icons: Home / Library / Search (current screen suppressed)
- Kebab: Playlists / Discover / Settings /
[Admin if isAdmin]
The new shared
MainAppBarActionswidget is adopted across all top-level screens for consistency. Admin entry is also discoverable via a Settings card (admin-only).Routing
buildRouterredirect closure now refuses any/admin/*path whenuser.isAdmin == false(serverRequireAdminmiddleware remains the authority — this is UX).Operator notes
- After upgrading the APK, admins will see a new "Admin" entry in the kebab menu and a card at the bottom of Settings.
- Non-admins see no UI changes besides the simpler AppBar.
- The next slice (TBD) will address the bootstrap-invite-token gap noticed during the last deploy: when a bootstrap admin already exists from
MINSTREL_AUTH_ADMIN_USERNAME, fresh self-registration via/registerfalls through to the invite-only mode and fails because no invite-generation UI is reachable. Now that admins can generate invites in the new Users screen, the operator workaround is "log in as bootstrap admin → generate invite → register with token", but a smoother default belongs in a follow-up.
Downloads
- Requests — list of pending Lidarr requests with Approve / Reject. Joins requester
-
v2026.05.08.2 Stable
released this
2026-05-08 18:02:06 -04:00 | 995 commits to main since this releaseHotfix release — unblocks bootstrap-admin self-registration in production, plus the M7 Flutter slice that landed alongside.
Web
- fix(web):
/registeris now reachable when logged out (#32). Closes user-mgmt umbrella #376 — first registered user becomes admin and can manage roles via/admin/users. Adds a regression-testedisPublicRoute()helper so this can't silently re-tighten.
Flutter (M7 mobile client)
- feat: Discover screen — search Lidarr and create requests directly from the mobile client.
- feat: Settings screen + 4-icon home AppBar.
- feat: Queue screen with
skipToQueueItemsupport. - feat: Library screen with 5 tabs (Artists / Albums / History / Liked / Hidden).
- feat: Playlists list + detail screens.
- feat: Search slice + dependency bumps to latest.
- fix(flutter/test): Tolerate Riverpod 3's
ProviderExceptionwrapper.
Docs
- docs(readme): Quickstart compose uses
:latestinstead of a pinned tag.
Operator notes
- Web UI requires HTTPS (or
localhost) — SvelteKit's client runtime callscrypto.randomUUID, which the browser only exposes in secure contexts. If the page renders blank with acrypto.randomUUID is not a functionconsole error, that's the cause. - After deploy, browse to
/registerwhile logged out to bootstrap your admin account — the first registered user is automatically markedis_admin = true.
Downloads
- fix(web):
-
released this
2026-05-08 13:46:23 -04:00 | 1005 commits to main since this releaseFirst release of the day. Two-round DRY pass across Go server + web SPA, plus PR3's cover-art provider HTTP plumbing extraction.
Server (Go)
- New
internal/apierrorpackage: typed*Error, sentinels, constructors. ~342writeErrcall sites converted towriteErr(err). resolveByID[T]helper for theparseUUID → fetch → 404pattern (~7 sites migrated).audit.WriteOrLog(ctx, pool, logger, ...)wrapper (~13 sites migrated).- Per-package
ErrNotFounddeclarations consolidated to aliasapierror.ErrNotFound. - Handler prelude helpers (
requireUser/requireURLUUID/decodeBody) — ~25 handler files cleaner. - sqlc
*ForUsertrack query pairs unified via nullable user_id. - Test fixtures:
withUserhelper migration (51 sites).
Web SPA
errCode/errMessagehelpers (38 sites).- Single
<Modal>component (8 admin modals). pushToaststore + global<ToastHost />(8 sites).offsetGetNextPageParam+pageGetNextPageParampaging helpers (8 sites).- Theme hex now reads from
tokens.json(single source). auth/user.svelte.tsextracted to break the auth↔player import cycle.- svelte-query mock factored into
vitest.setup.ts(-180 LoC across 34 test files). PlaylistTrackRowdrag-reorder migrated to@neodrag/svelte(matchesQueueTrackRow).- 5 open-coded
/api/albums/${id}/coversites →coverUrl()helper. .play-overlayCSS hoisted from 3 components toapp.css.
Cover-art (Go)
- Shared
httpClientstruct providing rate-limit + exponential backoff + retry + body-size limits. - Last.fm / Deezer / TheAudioDB providers now delegate HTTP plumbing — ~400 LoC removed across the trio.
Other
error-copy.jsonexpanded from 14 → 46 codes (auth/validation/404 flows), mirrored to Flutter.
Notes
Wire-format normalization across
/api/*errors: many handlers were emitting inconsistentunauthorized/auth_required/unauthenticatedcodes for the same condition. The DRY pass standardized onauth_required. Frontend error-copy was expanded so the change is invisible to users.Downloads
- New