fix(search): load album cover for song results (#984)

* fix(search): load album cover for song results

Live Search song rows carried the per-track `mf-…` coverArt id from
search3, which the cover pipeline does not resolve, so the thumbnails went
blank. Album rows worked because they use the album-scoped `al-…` id. A
song's search thumbnail is its album cover anyway — derive it from the
albumId so it loads, and show a music glyph when there is no album.

* docs: changelog for search song cover fix (#984)
This commit is contained in:
Frank Stellmacher
2026-06-04 13:05:42 +02:00
committed by GitHub
parent 19fdba006a
commit 631330ebfa
2 changed files with 18 additions and 9 deletions
+6
View File
@@ -907,6 +907,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
* The song rail's previous/next and reroll buttons are now square like every other rail instead of round.
### Search — song results show their album cover
**By [@Psychotoxical](https://github.com/Psychotoxical), reported by zunoz on Discord, PR [#984](https://github.com/Psychotoxical/psysonic/pull/984)**
* Song results in the search dropdown now show their album cover instead of leaving most thumbnails blank.
## [1.46.0] - 2026-05-18
> **🙏 Special thanks to [@zz5zz](https://github.com/zz5zz)** for his tireless quirk-spotting and bug reports on the [Psysonic Discord](https://discord.gg/AMnDRErm4u) — several of the polish fixes in this release landed directly off the back of his messages.
+12 -9
View File
@@ -34,7 +34,7 @@ import { AlbumCoverArtImage } from '../cover/AlbumCoverArtImage';
import { ArtistCoverArtImage } from '../cover/ArtistCoverArtImage';
import { CoverArtImage } from '../cover/CoverArtImage';
import { COVER_DENSE_SEARCH_CSS_PX } from '../cover/layoutSizes';
import { albumCoverRefForSong } from '../cover/ref';
import { albumCoverRef } from '../cover/ref';
import { showToast } from '../utils/ui/toast';
import { useShareSearch } from '../hooks/useShareSearch';
import ShareSearchResults from './search/ShareSearchResults';
@@ -71,11 +71,18 @@ function LiveSearchAlbumThumb({ albumId, coverArt }: { albumId: string; coverArt
}
function LiveSearchSongThumb({ song }: { song: Pick<SubsonicSong, 'id' | 'albumId' | 'coverArt' | 'discNumber'> }) {
// Search results carry the per-track `mf-…` coverArt id, which the cover
// pipeline fails to resolve and the thumbnail goes blank. The album-scoped
// `al-<albumId>_0` id is what actually loads (verified in the RC1 blank-thumb
// investigation), and a song's search thumbnail is its album cover anyway —
// so fetch the album cover from the albumId. Falls back to a music glyph when
// there is no album to key on.
const albumId = song.albumId?.trim();
const coverRef = React.useMemo(
() => (song.albumId?.trim() ? albumCoverRefForSong(song) : undefined),
[song.id, song.albumId, song.coverArt, song.discNumber],
() => (albumId ? albumCoverRef(albumId, `al-${albumId}_0`) : undefined),
[albumId],
);
if (!coverRef) return null;
if (!coverRef) return <div className="search-result-icon"><Music size={14} /></div>;
return (
<CoverArtImage
coverRef={coverRef}
@@ -797,11 +804,7 @@ export default function LiveSearch() {
openContextMenu(e.clientX, e.clientY, songToTrack(s), 'song');
}}
role="option" aria-selected={activeIndex === i}>
{(s.coverArt ?? s.albumId) ? (
<LiveSearchSongThumb song={s} />
) : (
<div className="search-result-icon"><Music size={14} /></div>
)}
<LiveSearchSongThumb song={s} />
<div>
<div className="search-result-name">{s.title}</div>
<div className="search-result-sub">{s.artist} · {s.album}</div>