feat(plex): shareable Plex-browse state in the URL — scope+filters+sort+show (S4)

Mirror the Plex-browse content state into the address bar so a browse view is
bookmarkable/shareable, the Plex analogue of the feed's urlState. Keys are
NAMESPACED (pscope/psort/pshow/pf) rather than reusing the feed's bare
scope/sort/show: FeedFiltersProvider actively strips its own KEYS from the URL
whenever off the feed page, so a shared key would be deleted out from under the
Plex page. The namespaced keys are Plex-unique (like ?v=/?pl), so no other
provider ever reads or clears them.

- New lib/plexUrlState.ts: plexParamUpdates / plexParamsToState / hasPlexParams.
  The pf filter blob reuses the SAME appendPlexFilters wire serializer (new
  public plexFiltersToParams + inverse parsePlexFilters, kept next to each other
  so a new wire key can't be added on one side only).
- PlexProvider: URL-seed wins over localStorage at mount; the account-pin
  rehydrate persists the seed instead of clobbering it (mirrors feed's
  initialHadUrlFilters); useUrlMirror mirrors/clears the 4 keys, gated on
  page===plex. q/field stay out of the shareable contract.
- NavigationProvider: a page switch now also nulls the 4 plex keys.

Tests: 8 new round-trip cases in plexUrlState.dom.test.ts (FE vitest 483→491).
This commit is contained in:
2026-08-27 03:14:54 +02:00
parent 8299ea72b7
commit 56a2ecdec3
6 changed files with 318 additions and 7 deletions
+13 -3
View File
@@ -126,12 +126,22 @@ export function NavigationProvider({ children }: { children: ReactNode }) {
// Push an in-app history entry so browser/mouse Back steps through pages instead of leaving the
// app. A fresh { sfPage } (no carried-over _yt/_chan markers) so each page starts at its root, and
// the address bar reflects the page (feed is implicit) while clearing the view-specific params
// (chan/v/pl) — a page switch resets to that module's root. Feed filters (owned by
// FeedFiltersProvider) are left intact.
// (chan/v/pl and the plex content keys pscope/psort/pshow/pf) — a page switch resets to that
// module's root. Feed filters (owned by FeedFiltersProvider) are left intact; the plex keys
// re-mirror from PlexProvider on return to the plex page.
window.history.pushState(
{ sfPage: next, sfScroll: nextNavId() },
"",
urlWithParams({ page: next === "feed" ? null : next, chan: null, v: null, pl: null }),
urlWithParams({
page: next === "feed" ? null : next,
chan: null,
v: null,
pl: null,
pscope: null,
psort: null,
pshow: null,
pf: null,
}),
);
notifyNav();
},