feat(plex): port the saved-views (personal views) UI to Plex (frontend)

Plex now has the same personal-views system as the feed — the rail header icon
opens the quick-pick popover, the rail title opens the manager modal (list /
create / rename / delete / reorder / set-default / apply), and the active view
leads the chip row with a "modified" dot.

One SavedViewsProvider serves both modules: it switches `source` (feed ⟷ plex)
by the active page (both filter providers are global, so it reads whichever the
page owns) and runs the same logic on per-source wiring — the live filters,
apply, a stable signature (feed filtersToParams / plex plexFiltersSignature),
coercion (asFilters / coercePlexFilters), and what to strip on save. Moved below
PlexProvider in the tree so it can read plex state; resets sticky/modal on a
source switch. The shared popover/modal/chip are unchanged; the modal describes
a plex view's captured filters via a pure buildPlexActiveFilters (extracted from
usePlexActiveFilters — no duplication). api.savedViews/createView take a source;
`SavedView.filters` is now an opaque blob. New coercePlexFilters/plexFilters-
Signature (also dedups PlexProvider's collection migration). Empty-state copy
genericised (no longer says "feed"). A Plex view captures the rail filters (not
watch-state/sort/scope).

Verified end-to-end on localdev: create/apply/preview/default/delete on plex,
feed views stay separate + unregressed (402 vitest), no console errors.
This commit is contained in:
2026-08-22 19:16:30 +02:00
parent fa7a09a420
commit a20e27800c
13 changed files with 254 additions and 97 deletions
+3
View File
@@ -30,6 +30,7 @@ import PlexDiscover from "./PlexDiscover";
import { PlexCollectionsBrowse, PlexPlaylistsBrowse } from "./plexBrowseLists";
import { PageToolbar } from "./PageShell";
import ActiveFilterChips from "./ActiveFilterChips";
import SelectedViewChip from "./SelectedViewChip";
import ItemCount from "./ItemCount";
import { StateMessage } from "./QueryState";
import { usePlexActiveFilters, usePlexClearFilters } from "./usePlexActiveFilters";
@@ -461,6 +462,8 @@ export default function PlexBrowse() {
left (overflowing to a "+N"/count pill so they never crowd the controls), while Sort +
the Discover/Browse toggle stay pinned to the right — the same layout as the Feed. */}
<div className="mt-1 flex items-center gap-2">
{/* The active personal view (if any) leads the applied-filter row, like the feed. */}
<SelectedViewChip />
<ActiveFilterChips
filters={plexChips}
onClearAll={clearAllFilters}