-
v2026.05.08.3 Stable
released this
2026-05-08 22:08:27 -04:00 | 1390 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 | 1407 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 | 1417 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