fix(plex): Restart only where an episode has a resume point; the Surprise draw is re-read, not redrawn

Show cards carry resumable (ShowState, from the same rollup) so a part-watched
show whose next episode is untouched offers Play, not a Continue and a Restart
that do the same thing; plexStarted is the one rule for cards and menus. The
Surprise rail's key sits under the discover root: whatever refreshes the rails
re-reads the draw (keep=) with its watch state now, kept past the 5-minute gc,
and only the reshuffle deals. Closing the player refreshes the Discover rails.
A show card's play acts on the last click only, and only where it was made.
This commit is contained in:
2026-10-09 22:01:28 +02:00
parent 5572499b56
commit 6df257d7df
26 changed files with 433 additions and 80 deletions
+20 -9
View File
@@ -1,4 +1,4 @@
import { type ReactNode } from "react";
import { useRef, type ReactNode } from "react";
import { useTranslation } from "react-i18next";
import { useQuery, useQueryClient } from "@tanstack/react-query";
import { RotateCcw } from "lucide-react";
@@ -120,25 +120,36 @@ export default function PlexDiscover({
queryFn: () => api.plexDiscover(scope, lang, filters),
});
// Surprise-me is its own query so the ↻ button can redraw it without refetching the whole surface.
// A redraw hands over the draw on screen, so a small filtered pool deals its other titles first.
// A draw stands until the ↻ (or another scope, language or filter) asks for a new one: redrawn on
// every refetch, the card just looked at was gone from its rail on Back from the player or a show
// page. So a refetch reads the draw on screen again (`keep`: fresh watch state, same titles), and
// only the ↻ deals — handing over that draw, so a small filtered pool deals its other titles first.
const surpriseKey = qk.plexSurprise(scope, lang, filters);
const redraw = useRef(false);
const surpriseQ = useQuery({
queryKey: surpriseKey,
queryFn: () => {
const shown = qc.getQueryData<{ items: PlexCard[] }>(surpriseKey)?.items ?? [];
const shown = (qc.getQueryData<{ items: PlexCard[] }>(surpriseKey)?.items ?? []).map(
(c) => c.id,
);
const deal = redraw.current || shown.length === 0;
redraw.current = false;
return api.plexSurprise(
scope,
undefined,
filters,
lang,
shown.map((c) => c.id),
deal ? { exclude: shown } : { keep: shown },
);
},
// A draw stands until ↻ (or another scope, language or filter) asks for a new one. A stale one was
// redrawn on every remount — Back from the player or a show page — and on a return to the window,
// so the card just looked at was gone from the rail it came from.
staleTime: Infinity,
// Kept past the default five minutes unobserved: a film watched from the rail outlasts that, and
// a collected draw is a new one.
gcTime: Infinity,
});
const reshuffle = () => {
redraw.current = true;
void surpriseQ.refetch();
};
// Watched toggle over BOTH this surface's cache (so a rail card flips instantly) and the surprise
// list, then persist + resync via the shared helper — invalidating the grid too so it agrees.
@@ -243,7 +254,7 @@ export default function PlexDiscover({
cards={surprise}
action={
<IconButton
onClick={() => surpriseQ.refetch()}
onClick={reshuffle}
loading={surpriseQ.isFetching}
label={t("plex.discover.reshuffle")}
>