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.
Lets any profile pick which admin-created credential set is active for it,
without creating/seeing secrets:
- GET /api/profiles/me/services per-service options (id+label only) +
this profile's selected_id (stale-safe)
- POST /api/profiles/me/services/select {service, credential_id|null}
Not admin-gated by design — it only writes a per-profile pointer and exposes no
secrets. Validates the chosen set exists AND belongs to that service (can't
select a tidal set under spotify), and rejects unsupported services. null clears
back to the global/admin default.
Tests: a non-admin reads options + selects + clears (no secret in the response),
and selection rejects wrong-service / nonexistent / unsupported. 10 endpoint
tests total.
Admin-only CRUD over the named credential sets from Phase 0:
- GET /api/credentials list all sets grouped by service (NO secrets)
- POST /api/credentials create {service,label,payload}, validated
- PUT /api/credentials/<id> update label and/or payload (partial)
- DELETE /api/credentials/<id> delete (clears any profile selections → fallback)
All four are @admin_only (non-admin → 403), payloads validated via
core.credentials.store, secrets never returned to the browser. Additive — no
existing endpoint or behaviour changes.
Tests: real web_server app + Flask test client (8) — create/list/update/delete
roundtrip, payload never leaks in list, missing-field/unsupported-service/blank-
label/duplicate(409)/404 validation, and the non-admin 403 gate on every write.
Verified the web_server import coexists with the rest of the suite (175 mixed
tests pass).