• bvandeusen released this 2026-05-11 11:22:10 -04:00 | 828 commits to main since this release

    Release 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

    bvandeusen released this 2026-05-10 23:20:59 -04:00 | 857 commits to main since this release

    Two 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 the version field stamped at build time via MINSTREL_VERSION build-arg → ldflag).

    Verification after deploy

    1. Download button: Settings → scroll to Mobile app section → "Get the Android app · v2026.05.10.2 · ~62 MB" link should be visible.
    2. Server version: bottom of Settings page → "Server v2026.05.10.2" subtle text.
    3. Healthz includes version: curl https://<host>/healthz → JSON should include "version": "v2026.05.10.2".
    4. 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
  • v2026.05.10.1 Stable

    bvandeusen released this 2026-05-10 21:45:22 -04:00 | 862 commits to main since this release

    Re-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_TOKEN secret, which lets flutter.yml attach the APK to the release and release.yml bundle 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 as minstrel-v2026.05.10.1.apk
    • release.yml "Fetch APK for bundled in-app update channel" step succeeds within 15 min
    • Deployed image :v2026.05.10.1 has /app/client/minstrel.apk populated (docker exec ... ls /app/client/)
    • curl -H 'Cookie: <session>' /api/client/version returns the version JSON

    Test plan (carried from PR #35)

    1. Offline browse — first sync after upgrade fully populates drift; library/artist/album surfaces should show real names instead of empty rows.
    2. In-app update — existing client should detect this release on next 24h poll → banner appears → install via system dialog.
    3. add-to-playlist sheet — system playlists (For You / Discover) should be hidden.
    4. Mobile responsive — walk through at 375 / 414 / 768 px.
    5. Stream resilience — toggle airplane mode briefly during playback; auto-retry should kick in.
    Downloads
  • v2026.05.10.0 Stable

    bvandeusen released this 2026-05-10 21:10:04 -04:00 | 864 commits to main since this release

    First 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 + Android PackageInstaller intent. 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

    1. Offline browse — first sync after upgrade fully populates drift; library/artist/album surfaces should show real names instead of empty rows.
    2. In-app update — existing client should detect this release on next 24h poll → banner appears → install via system dialog.
    3. add-to-playlist sheet — system playlists (For You / Discover) should be hidden.
    4. Mobile responsive — walk through at 375 / 414 / 768 px.
    5. Stream resilience — toggle airplane mode briefly during playback; auto-retry should kick in.
    Downloads
  • v2026.05.08.4 Stable

    bvandeusen released this 2026-05-08 22:20:03 -04:00 | 976 commits to main since this release

    Removes 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.go and its test — gone
    • auth.Bootstrap() startup call removed from cmd/minstrel/main.go
    • AuthConfig + AdminBootstrapConfig structs removed from internal/config
    • MINSTREL_AUTH_ADMIN_USERNAME / MINSTREL_AUTH_ADMIN_PASSWORD env vars no longer read
    • auth: block removed from config.example.yaml
    • Bootstrap-related comments removed from docker-compose.yml
    • README quickstart now points at /register instead of "watch logs for password"

    Net diff: +1 / -303 across 8 files.

    New first-run flow

    1. docker compose up
    2. Visit http://<host>/register
    3. First user on an empty users table becomes admin automatically (existing CreateUserFirstAdminRace path — 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, since users is 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_only after user #1 — generate invite tokens from /admin/users for additional users. UI for switching to open registration 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

    bvandeusen released this 2026-05-08 22:08:27 -04:00 | 978 commits to main since this release

    Flutter 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 via adminUsersProvider.
    • QuarantineExpansionTile per 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 a TypedConfirmSheet requiring 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 AdminLandingScreen with three section cards driven by adminCountsProvider (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 MainAppBarActions widget is adopted across all top-level screens for consistency. Admin entry is also discoverable via a Settings card (admin-only).

    Routing

    buildRouter redirect closure now refuses any /admin/* path when user.isAdmin == false (server RequireAdmin middleware 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 /register falls 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
  • v2026.05.08.2 Stable

    bvandeusen released this 2026-05-08 18:02:06 -04:00 | 995 commits to main since this release

    Hotfix release — unblocks bootstrap-admin self-registration in production, plus the M7 Flutter slice that landed alongside.

    Web

    • fix(web): /register is 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-tested isPublicRoute() 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 skipToQueueItem support.
    • 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 ProviderException wrapper.

    Docs

    • docs(readme): Quickstart compose uses :latest instead of a pinned tag.

    Operator notes

    • Web UI requires HTTPS (or localhost) — SvelteKit's client runtime calls crypto.randomUUID, which the browser only exposes in secure contexts. If the page renders blank with a crypto.randomUUID is not a function console error, that's the cause.
    • After deploy, browse to /register while logged out to bootstrap your admin account — the first registered user is automatically marked is_admin = true.
    Downloads
  • bvandeusen released this 2026-05-08 13:46:23 -04:00 | 1005 commits to main since this release

    First 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/apierror package: typed *Error, sentinels, constructors. ~342 writeErr call sites converted to writeErr(err).
    • resolveByID[T] helper for the parseUUID → fetch → 404 pattern (~7 sites migrated).
    • audit.WriteOrLog(ctx, pool, logger, ...) wrapper (~13 sites migrated).
    • Per-package ErrNotFound declarations consolidated to alias apierror.ErrNotFound.
    • Handler prelude helpers (requireUser / requireURLUUID / decodeBody) — ~25 handler files cleaner.
    • sqlc *ForUser track query pairs unified via nullable user_id.
    • Test fixtures: withUser helper migration (51 sites).

    Web SPA

    • errCode / errMessage helpers (38 sites).
    • Single <Modal> component (8 admin modals).
    • pushToast store + global <ToastHost /> (8 sites).
    • offsetGetNextPageParam + pageGetNextPageParam paging helpers (8 sites).
    • Theme hex now reads from tokens.json (single source).
    • auth/user.svelte.ts extracted to break the auth↔player import cycle.
    • svelte-query mock factored into vitest.setup.ts (-180 LoC across 34 test files).
    • PlaylistTrackRow drag-reorder migrated to @neodrag/svelte (matches QueueTrackRow).
    • 5 open-coded /api/albums/${id}/cover sites → coverUrl() helper.
    • .play-overlay CSS hoisted from 3 components to app.css.

    Cover-art (Go)

    • Shared httpClient struct 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.json expanded from 14 → 46 codes (auth/validation/404 flows), mirrored to Flutter.

    Notes

    Wire-format normalization across /api/* errors: many handlers were emitting inconsistent unauthorized / auth_required / unauthenticated codes for the same condition. The DRY pass standardized on auth_required. Frontend error-copy was expanded so the change is invisible to users.

    Downloads