diff --git a/CHANGELOG.md b/CHANGELOG.md
index 1ac99738..7acd06e4 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -289,6 +289,12 @@ Foundational work: faster reviews, narrower diffs, and a safety net under the pa
* **Add Station** / **Edit** now mount **`RadioEditModal`** with **`createPortal(..., document.body)`** (same layering approach as **Search Directory**). Previously the overlay lived under nested **`.content-body`** with **`contain: paint`**, so on an **empty** station list the fixed overlay was **painted inside a short box** and looked cropped; after the first station the layout was tall enough that the bug was easy to miss.
+### Now Playing — stable list keys on dashboard cards (duplicate React `key` warnings)
+
+**By [@cucadmuh](https://github.com/cucadmuh), PR [#703](https://github.com/Psychotoxical/psysonic/pull/703)**
+
+* **Similar artists**, the **track list inside the in-player album card**, and **Top songs** can all include **more than one row with the same Subsonic `id`** (server payloads are not always strictly deduped). Those lists used **`key={id}`**, which triggered **duplicate `key`** warnings in React devtools and made reconciliation depend on row order. Keys now combine **`id` with the list index** — rendering only; **no queue or playback change**.
+
### Search — hide duplicate artist hits with zero albums
**By [@cucadmuh](https://github.com/cucadmuh), thanks to zunoz for the report on the Psysonic Discord, PR [#697](https://github.com/Psychotoxical/psysonic/pull/697)**
diff --git a/src/components/nowPlaying/AlbumCard.tsx b/src/components/nowPlaying/AlbumCard.tsx
index 57144abe..544397ce 100644
--- a/src/components/nowPlaying/AlbumCard.tsx
+++ b/src/components/nowPlaying/AlbumCard.tsx
@@ -69,10 +69,10 @@ const AlbumCard = memo(function AlbumCard({ album, songs, currentTrackId, albumN
- {visibleSongs.map(track => {
+ {visibleSongs.map((track, idx) => {
const isActive = track.id === currentTrackId;
return (
-
{isActive
diff --git a/src/components/nowPlaying/ArtistCard.tsx b/src/components/nowPlaying/ArtistCard.tsx
index 59c5a6ec..21725706 100644
--- a/src/components/nowPlaying/ArtistCard.tsx
+++ b/src/components/nowPlaying/ArtistCard.tsx
@@ -81,8 +81,8 @@ const ArtistCard = memo(function ArtistCard({ artistName, artistId, artistInfo,
{similar.length > 0 && (
- {similar.slice(0, 12).map(a => (
-
(
+ a.id && onNavigate(`/artist/${a.id}`)}
data-tooltip={t('nowPlaying.goToArtist')}>
{a.name}
diff --git a/src/components/nowPlaying/TopSongsCard.tsx b/src/components/nowPlaying/TopSongsCard.tsx
index 239c781a..6975af3f 100644
--- a/src/components/nowPlaying/TopSongsCard.tsx
+++ b/src/components/nowPlaying/TopSongsCard.tsx
@@ -35,7 +35,7 @@ const TopSongsCard = memo(function TopSongsCard({ artistName, artistId, songs, c
{top.map((s, idx) => {
const isActive = s.id === currentTrackId;
return (
- onPlay(s)}
data-tooltip={t('contextMenu.playNow')}>
diff --git a/src/config/settingsCredits.ts b/src/config/settingsCredits.ts
index 89f052f9..0b338ad1 100644
--- a/src/config/settingsCredits.ts
+++ b/src/config/settingsCredits.ts
@@ -110,6 +110,7 @@ const CONTRIBUTOR_ENTRIES = [
'OpenSubsonic albumArtists in album header; track artists in player bar, mobile, mini + songToTrack (#552) (PR #696)',
'Search: filter search3 artist rows with zero albums (report: zunoz on Psysonic Discord) (PR #697)',
'Internet Radio: portal add/edit station modal to document.body — fixes clipped modal on empty library (report: voidboywannabe on Psysonic Discord) (PR #699)',
+ 'Now Playing: composite list keys on similar artists, album-card tracklist, and top songs — avoids duplicate React keys when Subsonic repeats ids (PR #703)',
],
},
{