fix(nav,plex,playlists): extend account-pin re-hydrate to every affected provider
The fresh-tab restore bug wasn't Channels-specific: any provider that mounts above <App> and reads per-account state at useState-init hit it, since <App> pins the tab's account only after /api/me. Audited all of them — FeedFilters/FeedView/Prefs/App already self-correct via their own me?.id server-prefs adopt effects, so the still-broken ones were the pure-localStorage providers with no server adopt: NavigationProvider (landing page), PlexProvider (scope/show/sort/filters/q), PlaylistsProvider (selected playlist). Each now adopts the shared useAccountRehydrate hook (markDirty in the setters; page uses replaceState so the restore doesn't add a phantom Back step). Also baked re-hydrate into useAccountPersistedState/Object so their consumers (persisted tabs, player prefs) are robust even if one ever mounts before the pin. Self-E2E'd on a genuinely fresh tab: it lands on Plex with Movies/In-progress/Year/search all restored on the FIRST load.
This commit is contained in:
@@ -12,6 +12,7 @@ import { useTranslation } from "react-i18next";
|
||||
import { isPage, readPage, type Page } from "../lib/urlState";
|
||||
import { pageTitleKey } from "../lib/pageMeta";
|
||||
import { getAccountRaw, LS, setAccountRaw } from "../lib/storage";
|
||||
import { useAccountRehydrate } from "../lib/useAccountRehydrate";
|
||||
import { setNavigator } from "../lib/nav";
|
||||
|
||||
// Owns the app's navigation + browser-history state, lifted out of the App god component so pages
|
||||
@@ -103,18 +104,35 @@ export function NavigationProvider({ children }: { children: ReactNode }) {
|
||||
const channelViewRef = useRef(channelView);
|
||||
channelViewRef.current = channelView;
|
||||
|
||||
const setPage = useCallback((next: Page) => {
|
||||
// A channel page overlays the content column, so a nav click must close it even when the
|
||||
// underlying page is unchanged (e.g. "Feed" while a channel opened from the feed).
|
||||
if (next === pageRef.current && !channelViewRef.current) return;
|
||||
setChannelView(null); // leave any open channel page when navigating via the rail
|
||||
setPageState(next);
|
||||
setAccountRaw(PAGE_KEY, next);
|
||||
// Push an in-app history entry so browser/mouse Back steps through pages instead of leaving the
|
||||
// app. The URL stays clean — the page rides in history.state. A fresh { sfPage } (no carried-over
|
||||
// _yt/_chan markers) so each page starts at its root.
|
||||
window.history.pushState({ sfPage: next }, "");
|
||||
}, []);
|
||||
// The landing page is per-account (PAGE_KEY), but this provider mounts above <App>, which pins the
|
||||
// tab's account only after /api/me — so on a fresh tab `loadInitialPage` reads an unknown account and
|
||||
// falls back to "feed". Re-read once the account is pinned and correct the CURRENT history entry
|
||||
// (replaceState, not push) so Back isn't given a phantom "feed" step. markDirty (set by setPage)
|
||||
// skips this if the user already navigated in the pre-pin window.
|
||||
const markDirty = useAccountRehydrate(() => {
|
||||
const stored = loadInitialPage();
|
||||
if (stored === pageRef.current) return;
|
||||
setPageState(stored);
|
||||
setAccountRaw(PAGE_KEY, stored); // no-op re-write, keeps storage authoritative
|
||||
window.history.replaceState({ sfPage: stored }, "");
|
||||
});
|
||||
|
||||
const setPage = useCallback(
|
||||
(next: Page) => {
|
||||
// A channel page overlays the content column, so a nav click must close it even when the
|
||||
// underlying page is unchanged (e.g. "Feed" while a channel opened from the feed).
|
||||
if (next === pageRef.current && !channelViewRef.current) return;
|
||||
markDirty();
|
||||
setChannelView(null); // leave any open channel page when navigating via the rail
|
||||
setPageState(next);
|
||||
setAccountRaw(PAGE_KEY, next);
|
||||
// Push an in-app history entry so browser/mouse Back steps through pages instead of leaving the
|
||||
// app. The URL stays clean — the page rides in history.state. A fresh { sfPage } (no carried-over
|
||||
// _yt/_chan markers) so each page starts at its root.
|
||||
window.history.pushState({ sfPage: next }, "");
|
||||
},
|
||||
[markDirty],
|
||||
);
|
||||
|
||||
const openChannel = useCallback((id: string, name?: string) => {
|
||||
setChannelView({ id, name });
|
||||
|
||||
Reference in New Issue
Block a user