fix: show album artist on featured compilation cards (#979)

* fix: show album artist on featured compilation cards

The 'Also featured on' cards are synthesised from search3 child songs,
which only read the flat `albumArtist` field. Compilation children leave
that empty — the album-artist credit lives in OpenSubsonic's structured
`albumArtists` / `displayAlbumArtist` — so the card fell back to the '—'
placeholder. Carry the structured credit (and the display string) onto
the synthesised album so it resolves a name (and stays navigable when the
server supplies an id).

* docs: changelog for featured-compilation artist credit (#979)
This commit is contained in:
Frank Stellmacher
2026-06-04 09:54:10 +02:00
committed by GitHub
parent 47e16ebfef
commit a7c79ee210
4 changed files with 46 additions and 1 deletions
+6
View File
@@ -872,6 +872,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
* Album cards on a **Genre** page split multi-artist credits into individually clickable artist links, matching the rest of the app.
* On the **Artists** page the `#` index button now holds only names that start with a number; accented and non-Latin names (Æ Ø Å, Chinese, Japanese, Cyrillic, …) move to a new **Other** section instead of the `#` catch-all.
### Artist detail — credit on "Also featured on" compilations
**By [@Psychotoxical](https://github.com/Psychotoxical), PR [#979](https://github.com/Psychotoxical/psysonic/pull/979)**
* Compilation albums under **Also featured on** show their album artist (e.g. *Various Artists*) again instead of a bare `—`, and the credit links to the artist when the server provides one.
## [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.
+2
View File
@@ -72,6 +72,8 @@ export interface SubsonicSong {
genre?: string;
path?: string;
albumArtist?: string;
/** OpenSubsonic: single-string album-artist for display (mirrors `albumArtists` joined). */
displayAlbumArtist?: string;
/** ISRC code when available (e.g., Navidrome) */
isrc?: string;
/** Times the track has been played, surfaced by Navidrome's Subsonic API. */
+32
View File
@@ -72,6 +72,38 @@ describe('useArtistDetailData — id-gated info', () => {
await waitFor(() => expect(result.current.info).toEqual({ largeImageUrl: 'B.jpg' }));
});
it('keeps the album-artist credit on featured compilation albums', async () => {
// "Also featured on" synthesises albums from search3 child songs. A
// compilation has no flat `albumArtist` on the child — the credit lives in
// OpenSubsonic's structured `albumArtists` (and/or `displayAlbumArtist`).
// Dropping it made the card render "—" instead of "Various Artists".
vi.mocked(getArtist).mockResolvedValue({ artist: { id: 'A', name: 'A' }, albums: [] } as any);
vi.mocked(search).mockResolvedValue({
artists: [],
albums: [],
songs: [
{
id: 's1', title: 'Track', artistId: 'A', artist: 'A',
album: 'A Compilation', albumId: 'comp1', coverArt: 'c1', duration: 100,
albumArtists: [{ id: 'va', name: 'Various Artists' }],
},
{
id: 's2', title: 'Other', artistId: 'A', artist: 'A',
album: 'Display Only', albumId: 'comp2', coverArt: 'c2', duration: 90,
displayAlbumArtist: 'Various Artists',
},
],
} as any);
const { result } = renderHook(() => useArtistDetailData('A'));
await waitFor(() => expect(result.current.featuredAlbums).toHaveLength(2));
const structured = result.current.featuredAlbums.find(a => a.id === 'comp1');
const displayOnly = result.current.featuredAlbums.find(a => a.id === 'comp2');
expect(structured?.artists).toEqual([{ id: 'va', name: 'Various Artists' }]);
expect(displayOnly?.artist).toBe('Various Artists');
});
it('ignores a late-arriving resolve for a stale id', async () => {
vi.mocked(getArtist).mockImplementation(async (id) => (
{ artist: { id, name: id }, albums: [] } as any
+6 -1
View File
@@ -150,8 +150,13 @@ export function useArtistDetailData(
albumMap.set(song.albumId, {
id: song.albumId,
name: song.album,
artist: song.albumArtist ?? '',
// search3 children carry the album-artist credit in OpenSubsonic's
// structured `albumArtists` / `displayAlbumArtist` (e.g. "Various
// Artists" on compilations), not the flat `albumArtist` field — keep
// all of them so the card resolves a name instead of "—".
artist: song.albumArtist ?? song.displayAlbumArtist ?? '',
artistId: '',
artists: song.albumArtists,
coverArt: song.coverArt,
songCount: 1,
duration: song.duration,