• v2026.05.08.3 Stable

    bvandeusen released this 2026-05-08 22:08:27 -04:00 | 1390 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.
    • Quarantine — ExpansionTile 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 | 1407 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 | 1417 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