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>
1. lib/app.dart + lib/shared/routing.dart — buildRouter takes a Ref but was
being called from a ConsumerWidget's build() with a WidgetRef. Add a
routerProvider; the widget watches it instead of constructing the
router from its own ref. Real bug — would have crashed compile in
slice 1, just never compiled until CI ran.
2. lib/player/audio_handler.dart — override of AudioHandler.seek used
`p` for the Duration; rename to `position` to match the base class
(avoid_renaming_method_parameters lint).
3. lib/theme/theme_extension.dart — fromTokens() returned a non-const
constructor; all inputs are const so make the call const too
(prefer_const_constructors).
4. test/library/home_screen_test.dart — same const-constructor lint on
the HomeData test fixture.
5. test/smoke_test.dart — drop the unused
`import 'package:flutter/material.dart'`.
These are exactly the kind of issues the no-in-task-tests rule shifted
to CI; this is the first run that actually exercises the analyzer
against the slice 1 code.
audio_service runs the handler in a background isolate; UI watches
playbackState/mediaItem/queue streams through Riverpod. AndroidManifest
gets the foreground-service media-playback permission; Info.plist gets
UIBackgroundModes=audio. Bearer token attached to stream URLs via
just_audio's per-source headers.