fix(ui): a read-ahead waits out a background refetch, and a revealed row is mounted before it is focused

NEXT_PAGE joins ANY fetch in flight: a read-ahead during an invalidation
or a poll joined that refetch and appended no page, and nothing asked
again until the reader moved the range (the channel page during a
backfill). The read-ahead now waits while the list fetches anything
(mayReadAhead, re-run on isFetching); a Load older click cancels a poll
instead (nextPageOnClick).

A page turn to a revealed row restarted the progressive mount at 30
rows; Channels focuses that row in the next commit, and at index 40 of
a 50-row page it was not there, so focus fell to <body>. The mount now
reaches through the revealed row.
This commit is contained in:
2026-10-02 23:42:31 +02:00
parent ac53a3b757
commit 15ec67b029
11 changed files with 165 additions and 28 deletions
+4 -4
View File
@@ -9,7 +9,7 @@ import {
} from "@tanstack/react-query";
import { User, X } from "lucide-react";
import { qk } from "../lib/queryKeys";
import { NEXT_PAGE } from "../lib/nextPage";
import { mayReadAhead, NEXT_PAGE } from "../lib/nextPage";
import { healPlexField } from "../lib/plexSearch";
import { healPlexSort, plexSortOptions, PLEX_SORT_FALLBACK } from "../lib/plexSort";
import type { SortState } from "../lib/columnSort";
@@ -317,19 +317,19 @@ export default function PlexBrowse() {
// mount/unmount, so the observer always watches the live node; a plain ref would keep observing the
// detached old node after Back and never fire fetchNextPage again (feed stuck at the loaded pages).
const [sentinelEl, setSentinelEl] = useState<HTMLDivElement | null>(null);
const { hasNextPage, isFetchingNextPage, fetchNextPage } = browseQ;
const { hasNextPage, isFetching, isFetchingNextPage, fetchNextPage } = browseQ;
useEffect(() => {
if (!sentinelEl) return;
const io = new IntersectionObserver(
(entries) => {
if (entries[0]?.isIntersecting && hasNextPage && !isFetchingNextPage)
if (entries[0]?.isIntersecting && mayReadAhead(hasNextPage, isFetching))
void fetchNextPage(NEXT_PAGE);
},
{ rootMargin: "600px" },
);
io.observe(sentinelEl);
return () => io.disconnect();
}, [sentinelEl, hasNextPage, isFetchingNextPage, fetchNextPage]);
}, [sentinelEl, hasNextPage, isFetching, fetchNextPage]);
function onCard(card: PlexCard) {
scrollRef.current = scroller()?.scrollTop ?? 0; // remember grid position for the trip back