fix(mainstage): rename Home Page setting, start-page fallback + empty state (#975)

* fix(mainstage): rename Home Page setting, add start-page fallback + empty state

- Rename "Home Page" personalisation section to "Mainstage" across 9 locales
  so the heading matches the sidebar entry; add "mainstage" search keyword.
- Index route "/" now redirects to the first visible library item when the
  Mainstage sidebar entry is hidden, instead of stranding the app on a blank
  page. New pure resolveStartRoute() mirrors sidebar order + nav-mode gating.
- Show a guided empty state on Mainstage when every section is toggled off,
  with a CTA into Settings -> Personalisation.
- Unit tests for resolveStartRoute.

* docs(changelog): Mainstage rename, start-page fallback + empty state (#975)
This commit is contained in:
Frank Stellmacher
2026-06-04 02:12:52 +02:00
committed by GitHub
parent 3be8c367dd
commit 908c349cfd
24 changed files with 175 additions and 20 deletions
+20
View File
@@ -298,6 +298,11 @@ export default function Home() {
mostPlayed.length === 0 &&
recentlyPlayed.length === 0 &&
starred.length === 0;
// Every section toggled off in Settings → Personalisation → Mainstage. The
// page would otherwise be entirely blank, so surface a guided empty state
// pointing back at the toggles (or the option to hide Mainstage from the
// sidebar) instead of leaving the user on nothing.
const allSectionsHidden = homeSections.every(s => !s.visible);
return (
<div className={`animate-fade-in${homeLiteArtworkFx ? ' home-lite-artwork' : ''}${homeFlatArtworkClip ? ' home-flat-artwork-clip' : ''}`}>
{!loading && !perfFlags.disableMainstageHero && isVisible('hero') && <Hero albums={heroAlbums} />}
@@ -307,6 +312,21 @@ export default function Home() {
<div style={{ display: 'flex', justifyContent: 'center', padding: '4rem' }}>
<div className="spinner" />
</div>
) : allSectionsHidden ? (
<div className="empty-state" style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: '0.75rem' }}>
<div style={{ fontSize: 18, fontWeight: 600, color: 'var(--text-primary)' }}>
{t('home.mainstageEmptyTitle')}
</div>
<div style={{ maxWidth: 460 }}>{t('home.mainstageEmptyBody')}</div>
<button
type="button"
className="btn btn-primary"
style={{ marginTop: '0.5rem' }}
onClick={() => navigate('/settings', { state: { tab: 'personalisation' } })}
>
{t('home.mainstageEmptyCta')}
</button>
</div>
) : libraryEmpty ? (
<div className="empty-state" style={{ padding: '4rem 1rem', textAlign: 'center' }}>
{t('common.libraryEmpty')}