Focus chrome was implemented twice, once in the focusable wrapper and once
in the focus builders; both now go through FocusChrome. TvColorPicker's
channel row was a copy of TvNumberSpinner and is now that widget in compact
density.
Also trims unused helpers and fields and simplifies the Jellyfin browse
paths.
Un-gates music libraries; adds CardShape.square plumbing (circular
artists), artists/albums/tracks browse groupings with per-grouping
sorts, audio playlists tab, artist + album detail screens with dpad
track rows, music context-menu actions, and a stub MusicPlaybackService
seam for the upcoming engine.
Signing in triggered two back-to-back profile rebinds; the second re-added
the same Jellyfin connection, which tore down the live client and aborted
the home screen's in-flight fetches. The aborted pass was committed as
loaded-empty, flashing 'no content available' until the follow-up load
landed. Fix at the root instead of patching the sign-in window:
- addJellyfinConnection now reuses the live client when the connection is
unchanged (token, deviceId, URL set), matching the existing Plex
refreshTokensForProfile behavior; material changes still recreate it.
- Cancelled requests are classified end-to-end: the client's
treat-as-empty helpers rethrow cancellations, and the aggregation
fan-outs report cancelledServerIds alongside succeededServerIds.
- A fetch pass in which zero servers succeeded is never authoritative:
it keeps existing content instead of wiping it (also fixes the
pre-existing blanking of home/sidebar on a totally failed refresh),
stays in loading while disrupted (cancellation or binding in flight),
and only commits loaded-empty on a settled failure.
Discover and libraries now fetch only the servers that came online since
the last pass and merge the results; already-loaded servers are not
refetched. Both providers also own their online-servers listener
(registered in the constructor, removed in dispose), so profile switches
no longer leak listeners that kept disposed providers refetching.