fix(library): empty-state on Mainstage, Albums, New Releases, Random Albums (#750)

* fix(library): show empty-state on Mainstage, Albums, New Releases, Random Albums

When the active library has no albums, Mainstage and the three album-list
pages rendered a fully blank page — every rail and grid was empty but
nothing told the user why. Add a shared `common.libraryEmpty` message
(all 9 locales) and render it in place of the empty grid:

* Home: when no rail has any albums after loading.
* Albums: when no albums and no filter (genre/year/starred/comp) is
  active — filtered "no matches" is a separate state and stays as-is.
* New Releases: when no albums and no genre filter is active.
* Random Albums: when no albums after loading.

Pages that already had a dedicated empty-state (Artists, Genres,
Composers, Playlists, Favorites, MostPlayed, LosslessAlbums,
LabelAlbums, InternetRadio) are left alone — their wording is
intentional and per-page.

* docs(changelog): note empty-library states on Mainstage / album lists (#750)
This commit is contained in:
Frank Stellmacher
2026-05-17 14:10:36 +02:00
committed by GitHub
parent 9609da6bc2
commit 5856bdbf1a
14 changed files with 41 additions and 0 deletions
+6
View File
@@ -651,6 +651,12 @@ Foundational work: faster reviews, narrower diffs, and a safety net under the pa
* Collapsing and re-expanding **Settings → Audio → Equalizer** sometimes left the curve area blank: `ResizeObserver` does not reliably fire for the `display: none → block` transition the surrounding `<details>` triggers, so the redraw that depends on it never ran on the second open. A `toggle` listener on the parent `<details>` now redraws explicitly on open.
### Library — empty-state on Mainstage, Albums, New Releases and Random Albums
**By [@Psychotoxical](https://github.com/Psychotoxical), thanks to zunoz for the report on the Psysonic Discord, PR [#750](https://github.com/Psychotoxical/psysonic/pull/750)**
* Selecting an empty library no longer leaves these pages as a fully blank canvas. A shared `common.libraryEmpty` message ("Your library is empty.") added across all nine locales is shown in place of the empty rails/grids. Pages that already had a dedicated empty-state (Artists, Genres, Composers, Playlists, Favorites, Most Played, Lossless Albums, Label Albums, Internet Radio) keep their per-page wording. On Albums and New Releases, an active genre / year / starred / compilation filter still shows the regular filtered-results behaviour rather than the library-empty message.
## [1.45.0] - 2026-05-04
## Added
+1
View File
@@ -63,4 +63,5 @@ export const common = {
more: 'mehr',
yearRange: 'Jahresbereich',
clearAll: 'Alles zurücksetzen',
libraryEmpty: 'Deine Bibliothek ist leer.',
};
+1
View File
@@ -63,4 +63,5 @@ export const common = {
more: 'more',
yearRange: 'Year Range',
clearAll: 'Clear all',
libraryEmpty: 'Your library is empty.',
};
+1
View File
@@ -53,4 +53,5 @@ export const common = {
more: 'más',
yearRange: 'Rango de años',
clearAll: 'Limpiar todo',
libraryEmpty: 'Tu biblioteca está vacía.',
};
+1
View File
@@ -53,4 +53,5 @@ export const common = {
more: 'plus',
yearRange: 'Plage d\'années',
clearAll: 'Tout effacer',
libraryEmpty: 'Votre bibliothèque est vide.',
};
+1
View File
@@ -53,4 +53,5 @@ export const common = {
more: 'mer',
yearRange: 'Årsspenn',
clearAll: 'Tøm alt',
libraryEmpty: 'Biblioteket ditt er tomt.',
};
+1
View File
@@ -53,4 +53,5 @@ export const common = {
more: 'meer',
yearRange: 'Jaarbereik',
clearAll: 'Alles wissen',
libraryEmpty: 'Je bibliotheek is leeg.',
};
+1
View File
@@ -63,4 +63,5 @@ export const common = {
more: 'mai mult',
yearRange: 'Interval de an',
clearAll: 'Golește tot',
libraryEmpty: 'Biblioteca ta este goală.',
};
+1
View File
@@ -53,4 +53,5 @@ export const common = {
more: 'еще',
yearRange: 'Диапазон лет',
clearAll: 'Очистить всё',
libraryEmpty: 'Ваша библиотека пуста.',
};
+1
View File
@@ -53,4 +53,5 @@ export const common = {
more: '更多',
yearRange: '年份范围',
clearAll: '清除全部',
libraryEmpty: '你的音乐库是空的。',
};
+4
View File
@@ -310,6 +310,10 @@ export default function Albums() {
<div style={{ display: 'flex', justifyContent: 'center', padding: '3rem' }}>
<div className="spinner" />
</div>
) : !loading && albums.length === 0 && !genreFiltered && !yearActive && !starredOnly && compFilter === 'all' ? (
<div className="empty-state" style={{ padding: '3rem 1rem', textAlign: 'center' }}>
{t('common.libraryEmpty')}
</div>
) : (
<>
{!perfFlags.disableMainstageGridCards && (
+14
View File
@@ -183,6 +183,16 @@ export default function Home() {
const homeLiteArtworkFx = perfFlags.disableHomeArtworkFx;
const homeFlatArtworkClip = perfFlags.disableHomeArtworkClip;
// Treat the library as empty when every album endpoint returned zero. The
// song/artist rails can be empty for non-empty libraries (rare server quirks),
// so they don't count toward this signal.
const libraryEmpty =
!loading &&
recent.length === 0 &&
random.length === 0 &&
mostPlayed.length === 0 &&
recentlyPlayed.length === 0 &&
starred.length === 0;
return (
<div className={`animate-fade-in${homeLiteArtworkFx ? ' home-lite-artwork' : ''}${homeFlatArtworkClip ? ' home-flat-artwork-clip' : ''}`}>
{!perfFlags.disableMainstageHero && isVisible('hero') && <Hero albums={heroAlbums} />}
@@ -192,6 +202,10 @@ export default function Home() {
<div style={{ display: 'flex', justifyContent: 'center', padding: '4rem' }}>
<div className="spinner" />
</div>
) : libraryEmpty ? (
<div className="empty-state" style={{ padding: '4rem 1rem', textAlign: 'center' }}>
{t('common.libraryEmpty')}
</div>
) : (
<>
{!homeAlbumRowsDisabled && isVisible('recent') && (
+4
View File
@@ -178,6 +178,10 @@ export default function NewReleases() {
<div style={{ display: 'flex', justifyContent: 'center', padding: '3rem' }}>
<div className="spinner" />
</div>
) : !loading && albums.length === 0 && !filtered ? (
<div className="empty-state" style={{ padding: '3rem 1rem', textAlign: 'center' }}>
{t('common.libraryEmpty')}
</div>
) : (
<>
<VirtualCardGrid
+4
View File
@@ -180,6 +180,10 @@ export default function RandomAlbums() {
<div style={{ display: 'flex', justifyContent: 'center', padding: '4rem' }}>
<div className="spinner" />
</div>
) : !loading && albums.length === 0 ? (
<div className="empty-state" style={{ padding: '3rem 1rem', textAlign: 'center' }}>
{t('common.libraryEmpty')}
</div>
) : (
<VirtualCardGrid
items={albums}