feat: now-playing liveness dot + admin-gated radio management (#1086)

* feat(now-playing): liveness indicator dot in the listening popover

Replace the raw "Nm ago" line in the "Who is listening?" popover with a
derived presence dot (green playing / amber paused / dim idle). The presence
is computed in one tested helper that unifies the playbackReport transport
state with the legacy getNowPlaying recency, instead of formatting a raw
timestamp inline. The dot carries the localized status as an aria-label and
tooltip so it is not conveyed by colour alone.

* feat(radio): gate station create/edit/delete behind Navidrome admin role

Navidrome >= 0.62 restricts internet-radio management to admins
(GHSA-jw24-qqrj-633c); non-admin requests fail. Hide Add Station, Search
Directory, the per-card edit chip and delete button for confirmed standard
Navidrome users via a canManageNavidromeRadio() helper on the existing
useNavidromeAdminRole framework. Admins, non-Navidrome servers and transient
states stay unrestricted; playback and favourites remain available to all.

* docs(changelog): now-playing status dot + admin-gated radio (#1086)
This commit is contained in:
Psychotoxical
2026-06-14 01:30:25 +02:00
committed by GitHub
parent 0b7d9eae2d
commit c1403f8bd6
19 changed files with 231 additions and 44 deletions
+1
View File
@@ -36,6 +36,7 @@
@import './playlists-page.css';
@import './statistics-page.css';
@import './connection-indicator.css';
@import './now-playing-presence.css';
@import './offline-overlay.css';
@import './now-playing-page.css';
@import './now-playing-page-2.css';
@@ -0,0 +1,29 @@
/* ─ Now Playing presence LED ─
* Liveness dot in the "Who is listening?" popover. Mirrors the connection LED
* status colours; the derived presence comes from `nowPlayingPresence()`.
* Status is also exposed as the dot's aria-label / tooltip so it is never
* conveyed by colour alone. */
.now-playing-led {
width: 8px;
height: 8px;
border-radius: 50%;
flex-shrink: 0;
background: var(--text-muted);
transition: background 0.4s ease, box-shadow 0.4s ease;
}
.now-playing-led--playing {
background: #a6e3a1;
box-shadow: 0 0 6px 2px rgba(166, 227, 161, 0.5);
}
.now-playing-led--paused {
background: #f9e2af;
box-shadow: 0 0 5px 1px rgba(249, 226, 175, 0.45);
}
.now-playing-led--idle {
background: var(--text-muted);
box-shadow: none;
opacity: 0.5;
}