• bvandeusen released this 2026-05-13 22:37:08 -04:00 | 1159 commits to main since this release

    Substantial 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
  • bvandeusen released this 2026-05-11 23:58:58 -04:00 | 1205 commits to main since this release

    Polish + 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
  • bvandeusen released this 2026-05-11 20:36:13 -04:00 | 1214 commits to main since this release

    Substantial 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
  • bvandeusen released this 2026-05-11 15:51:52 -04:00 | 1231 commits to main since this release

    Two 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
  • bvandeusen released this 2026-05-11 13:47:55 -04:00 | 1234 commits to main since this release

    Flutter-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
  • bvandeusen released this 2026-05-11 11:22:10 -04:00 | 1240 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 | 1269 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 | 1274 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 | 1276 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 | 1388 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