Two issues from on-device testing against prod HTTPS:
1. ServerImage was resolving cover URLs correctly
(https://minstrel.fabledsword.com/api/albums/.../cover) but the server
returned 401 because Image.network doesn't carry the session token
automatically the way the browser sends cookies. Forwards the stored
session token as an Authorization: Bearer header. No-op when no token
is present.
2. The "Cleartext HTTP traffic to 127.0.0.1" audio error reproduced even
after the previous defensive check landed, which means the URL handed
to ExoPlayer has a valid scheme+host (just the wrong host). The check
only catches scheme-less URLs, so it didn't fire. Added debugPrint
logging at configure() and setQueueFromTracks() time to show the
actual baseUrl + per-track resolved URL on the next reproduction.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Cover-art Image.network calls were passing server-relative URLs
(/api/albums/<id>/cover) straight to NetworkImage, which interprets
"no scheme" as file:/// and crashes with "No host specified in URI".
Same root cause regardless of HTTPS or HTTP server.
ServerImage wraps Image.network with a Riverpod read of
serverUrlProvider and prefixes the configured base URL. Absolute URLs
(e.g. discover screen's Lidarr image_urls) pass through unchanged.
Three call sites updated: album_card, artist_card, playlists_list_screen.
discover_screen left as-is — its row.imageUrl is already absolute (Lidarr
returns full URLs from MusicBrainz / Spotify metadata) and it has a
meaningful errorBuilder that ServerImage doesn't expose.
Also adds a defensive check in audio_handler.setQueueFromTracks: if
the constructed stream URL ends up scheme-less, throw a StateError
naming baseUrl + track.streamUrl + track.id instead of letting it
fall through to ExoPlayer which surfaces a confusing "Cleartext HTTP
traffic to 127.0.0.1 not permitted" error (Android's URL parser
defaults a scheme-less URI to localhost). User reported this exact
confusing error against an HTTPS prod server; the better message
will pinpoint where the empty baseUrl comes from on next reproduction.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
CI caught six issues against the new admin slice:
- AsyncValue<T> in this Riverpod 3.3.1 codebase exposes `.value`
(returns T?), not `.valueOrNull`. Switched the three admin readers
to match the established convention (player_bar, queue_screen,
now_playing_screen all use `.value`).
- home_screen.dart still has ctx.push calls in _albumsRow / _artistsRow
(carousel tap handlers) — restored the go_router import that
Task 2 over-eagerly removed.
- admin_user_edit_sheet.dart had a single-line `if (!await ...) return;`
that violated curly_braces_in_flow_control_structures. Wrapped in
braces.
- admin_quarantine_item.dart doc comment had `<top>` placeholders that
the analyzer flagged as unintended HTML. Rephrased without angle
brackets.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Shared AppBar actions for top-level screens — three primary icons
(Home/Library/Search, current screen suppressed) plus a kebab containing
Playlists/Discover/Settings and (only when isAdmin) Admin. Replaces the
ad-hoc per-screen IconButton lists; centralises admin-gating logic in
one place so /admin entry can't accidentally leak to non-admins.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
ApiClient.buildDio takes on401; the library's dioProvider wires it to
the auth controller's clearSession. The router redirect already handles
navigation away from authed screens once the session goes null.
HomeScreen surfaces the banner on connection_refused with Retry +
Change URL.
Cold launch flow: no server-url -> /server-url. URL set, no token ->
/login. Token present -> /home with shell. VersionGate runs once when
the URL changes, hits /healthz, blocks if min_client_version > package
version. /home is a placeholder until Task 14.
Replaces Navigator.pushReplacementNamed in server_url_screen +
login_screen with context.go now that go_router owns the routes.