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
+23 -2
View File
@@ -1,7 +1,11 @@
import { lazy } from 'react';
import { Route, Routes } from 'react-router-dom';
import { Navigate, Route, Routes } from 'react-router-dom';
import MobilePlayerView from '../components/MobilePlayerView';
import { useIsMobile } from '../hooks/useIsMobile';
import { useSidebarStore } from '../store/sidebarStore';
import { useAuthStore } from '../store/authStore';
import { useLuckyMixAvailable } from '../hooks/useLuckyMixAvailable';
import { resolveStartRoute } from '../utils/componentHelpers/sidebarNavReorder';
// Route-level lazy loading: keeps the non-page graph (shell, player, stores) in
// the entry chunk; each page is fetched when its route is first visited.
@@ -36,6 +40,23 @@ const InternetRadio = lazy(() => import('../pages/InternetRadio'));
const Genres = lazy(() => import('../pages/Genres'));
const GenreDetail = lazy(() => import('../pages/GenreDetail'));
/**
* Index route ("/") = Mainstage. When the user has hidden Mainstage from the
* sidebar there is no nav link back to it, so landing on "/" would strand them
* on a page they deliberately removed (and which is blank when its sections are
* all off too). In that case redirect to the first visible library entry —
* mirroring the sidebar's own ordering — so the app never opens on a dead page.
*/
function MainstageRoute() {
const items = useSidebarStore(s => s.items);
const randomNavMode = useAuthStore(s => s.randomNavMode);
const luckyMixAvailable = useLuckyMixAvailable() && randomNavMode === 'separate';
const mainstageVisible = items.find(i => i.id === 'mainstage')?.visible ?? true;
if (mainstageVisible) return <Home />;
const target = resolveStartRoute(items, randomNavMode, luckyMixAvailable);
return target ? <Navigate to={target} replace /> : <Home />;
}
/**
* The main application route table. Rendered inside `AppShell`'s scroll
* viewport. `/now-playing` swaps to the mobile player view on narrow widths;
@@ -46,7 +67,7 @@ export default function AppRoutes() {
const isMobile = useIsMobile();
return (
<Routes>
<Route path="/" element={<Home />} />
<Route path="/" element={<MainstageRoute />} />
<Route path="/albums" element={<Albums />} />
<Route path="/tracks" element={<SearchBrowsePage />} />
<Route path="/random" element={<RandomLanding />} />