refactor(plex): unify Plex sort onto SortControl + a single SortState (R13 S1)

Plex sort was a bespoke chip-pill row plus a separate asc/desc chip pair, with
the key in PlexState.sort and the direction hidden in PlexFilters.sortDir. Now:
- sort is one SortState {key,dir}; sortDir is dropped from PlexFilters (and from
  the facet-key destructure). The wire call still splits it into sort + sort_dir
  at the server boundary, so the backend is untouched.
- The chip sort-row is replaced by the shared SortControl (select + arrow) — the
  same idiom the feed and channel manager use. New lib/plexSort.ts owns the
  scope-dependent vocabulary (movies add duration) + the title/asc fallback.
- PlexProvider migrates the old storage in place: a legacy bare-key plexSort
  string plus the old filters.sortDir become the {key,dir} state, so an existing
  sort preference survives the upgrade.
- PlexBrowse and PlexSidebar heal the stored sort against the current scope's
  options, so a movie-only key (duration) left over after switching to shows
  can't ask the server for an unavailable sort.

Gate green: tsc clean, eslint 0 errors, prettier clean, vitest 93.
This commit is contained in:
2026-08-13 23:36:34 +02:00
parent 440bdafa74
commit e4bdc5ea6a
5 changed files with 88 additions and 44 deletions
+11 -2
View File
@@ -9,6 +9,8 @@ import {
type ReactNode,
} from "react";
import { qk } from "../lib/queryKeys";
import { healSort } from "../lib/columnSort";
import { plexSortOptions, PLEX_SORT_FALLBACK } from "../lib/plexSort";
import { createPortal } from "react-dom";
import { useTranslation } from "react-i18next";
import {
@@ -90,6 +92,13 @@ export default function PlexBrowse() {
const { q, scope, show, sort, filters, playlistOpen: openPlaylist } = usePlex();
const { setScope, setFilters, setPlaylistOpen } = usePlexActions();
const dq = useDebounced(q.trim(), 350);
// The provider holds sort scope-agnostically; heal it against THIS scope's options so a movie-only
// key (e.g. duration) left over from before a switch to shows doesn't ask for an unavailable sort.
const sortState = healSort(
sort,
plexSortOptions(scope, () => ""),
PLEX_SORT_FALLBACK,
);
const sub = useHistorySubview<Sub>({ kind: "grid" });
// Sidebar asked to open a playlist → push it as a subview (browser Back returns to the grid), then
@@ -141,14 +150,14 @@ export default function PlexBrowse() {
}, [sub]);
const browseQ = useInfiniteQuery({
queryKey: qk.plexLibrary(scope, dq, sort, show, filters),
queryKey: qk.plexLibrary(scope, dq, sortState, show, filters),
enabled: !!scope && sub.view.kind === "grid",
initialPageParam: 0,
queryFn: ({ pageParam }) =>
api.plexLibrary({
scope,
q: dq || undefined,
sort,
sort: sortState,
show,
filters,
offset: pageParam as number,