// URL-derived navigation seeds, split out of NavigationProvider so they stay unit-testable AND the // provider file keeps react-refresh happy (component-only exports). `loadInitialPage` picks the landing // module from a bookmark's params; `channelViewFrom` is the shared _chan/?chan resolver behind BOTH the // channelView seed and the onPop handler. import { hasFilterParams, isPage, readPage, type Page } from "../lib/urlState"; import { getAccountRaw, LS } from "../lib/storage"; export const PAGE_KEY = LS.page; export interface ChannelView { id: string; name?: string; } export function loadInitialPage(): Page { const params = new URLSearchParams(window.location.search); if (params.has("page")) return readPage(); // A view-specific param implies its page, so a bookmarked deep link lands on the right module even // without an explicit ?page. Feed filters (or a channel overlay) imply the feed. if (params.has("v")) return "plex"; if (params.has("pl")) return "playlists"; if (hasFilterParams(params) || params.has("chan")) return "feed"; // A bare URL → the account's stored landing page. const stored = getAccountRaw(PAGE_KEY); return isPage(stored) ? stored : "feed"; } /** Resolve the open channel page from a landed history entry: the in-app `_chan` id (with its optional * `_chanName`), else the ?chan URL param — a fresh ?chan bookmark's entry is URL-only. The seed AND * the popstate handler share this ONE resolver so a popstate that lands on a bookmark entry (no * `_chan`) restores the channel from the URL instead of wrongly closing it. */ export function channelViewFrom( state: { _chan?: unknown; _chanName?: unknown } | null | undefined, search: string, ): ChannelView | null { const id = (state?._chan as string | undefined) ?? new URLSearchParams(search).get("chan") ?? undefined; return id ? { id, name: (state?._chanName as string) ?? undefined } : null; }