Active metadata source / media server / download source are app-wide
infrastructure, so the quick-switch modal is admin-only again:
openServiceSwitchModal() no-ops (with a toast) for non-admins, and the sidebar
Service Status loses its clickable affordance for them. Per-profile playlist
account selection will live on its own user-accessible surface, not here.
The Plex logo is a white wordmark, so it vanished on the modal's white logo
disc (it only shows on Settings because those toggles sit on dark). Added a
per-logo `dark` flag (Plex + SoulSync) that renders their disc dark (#1f2329)
across the hero, rail, and option cards, so the white logo is visible. Other
logos keep the white disc.
The modal's server logos were the odd ones out — Jellyfin used the wide
jellyfin.org wordmark and Navidrome a stretched navidrome.org image. Switched
the modal's _SS_SERVER_INFO (drives both the rail and the option grid) to clean
square icons: Plex's plex-logo.svg, Navidrome's tweakers.net icon, and the
homarr-labs Jellyfin PNG. Also swapped the Settings page Jellyfin toggle from
the jellyfin.org wordmark to the same homarr-labs icon so they match.
Correctness (the modal was lying): "Spotify (no auth)" is a COMPOSITE the
Settings page stores as fallback_source='spotify' + metadata.spotify_free=true,
not a literal 'spotify_free' value. The modal read the raw fallback_source and
showed plain "Spotify" as active even when Settings clearly said "(no auth)".
The endpoint now mirrors that mapping both ways — reports active='spotify_free'
when the flag is set, and switching to it writes fallback_source=spotify +
spotify_free=true (and clears the flag for any other source). Modal + Settings
now always agree.
Visual: the modal itself (not just the cards) is richer now —
- a hero header per tab: big brand-logo disc + "Active <kind> source" eyebrow +
the active name + a one-liner + an Active pill, all tinted by the brand color
with a soft radial glow (the Manage-Workers hero feel);
- the panel gained brand-tinted radial depth instead of flat black.
Test: spotify_free composite round-trips like Settings (stored split + reported
as spotify_free; flag clears on switch). 15 endpoint + 64 integrity tests pass.
Visual rework toward the Manage Workers feel:
- Cards are now circular brand-logo discs on white, with each service's brand
color (Spotify green, Deezer purple, Plex gold, …) driving the logo ring +
active glow/gradient + hover lift. Replaces the flat emoji tiles.
- The left rail is alive: each tab shows its category + the CURRENT active
choice's logo and label (e.g. "Metadata · Deezer"), with the active tab in a
brand-tinted gradient + accent bar — mirroring the worker rows.
Correctness fix (answers "modal says spotify, settings says spotify (no auth)"):
the modal read the RAW configured source, but the rest of the app shows the
EFFECTIVE one. get_primary_source() silently downgrades a configured 'spotify'
to the default (deezer) when Spotify isn't authenticated — so configured and
effective diverge. The endpoint now returns `effective` alongside `active`, and
the Metadata panel shows a note ("Configured source isn't connected — actually
using Deezer right now") whenever they differ. Settings was never broken; the
modal just wasn't showing the resolved source.
78 tests pass (integrity + endpoints); smoke confirms configured spotify →
effective deezer surfaces, spotify_free stays itself.
Replaces the basic credential-pill quick-switch with a Manage-Workers-styled
modal (topbar + left rail + panel, entrance animation, brand-logo cards).
- Sidebar Service Status: whole panel opens the modal; clicking the Metadata /
Media Server / Download rows deep-links straight to that tab. Removed the
"switch ▸" hover text.
- Three tabs: Metadata (source logo cards, unavailable ones dimmed), Server
(Plex/Jellyfin/Navidrome/SoulSync logos), Download (Single⇄Hybrid segmented
toggle; Hybrid shows a draggable priority list). Logos reuse SOURCE_LABELS +
HYBRID_SOURCES; active card gets an accent ring + check.
- Admin writes the GLOBAL active source/server/download (reuses the same setters
+ client reloads as the Settings save, so changes take effect immediately).
Non-admins see it read-only (editable=false) — the per-profile override is the
next layer.
Backend: GET /api/profiles/me/active-sources (any profile; reports editable),
POST /api/profiles/active-sources (@admin_only; validates against the allowed
metadata/server/download lists, applies + reloads). New service-switch.js
(registered + in the integrity registry); old modal removed from
credential-sets.js (admin Connected Accounts manager stays).
Tests: 14 endpoint tests — read shape, admin sets metadata/hybrid+order
(reflected), bad-value 400s, non-admin read-only + 403 on write. 64 integrity
tests pass; real-app smoke confirms render + deep-links + the full set/reflect
cycle.