fix(feed): an override yields only to a refetch that set out after its write was answered

Send order let a refetch read before an unanswered mark committed and drop it. A reset's position 0 is written again after a refetch that set out before the server took the reset. The player-session refetch also waits for the player's closing checkpoint, which the player now tracks (progressFlushes); refetched first, the list put the older position back.
This commit is contained in:
2026-10-09 23:46:39 +02:00
parent 8776586ca3
commit 2cff4ad954
7 changed files with 173 additions and 33 deletions
+13 -10
View File
@@ -19,7 +19,7 @@ import {
import { useBackToExit } from "./history";
import { api, type Video } from "./api";
import { qk } from "./queryKeys";
import { patchCachedProgress } from "./progressCache";
import { patchCachedProgress, trackProgressFlush } from "./progressCache";
import type { YTNamespace, YTPlayer, YTPlayerEvent } from "./youtube";
import { logYouTubeApiFailed, logYouTubeError, logYouTubeState } from "./mediaLog";
import type { AutoMode, LoopMode } from "./playerModes";
@@ -527,15 +527,18 @@ export function useYtProgress({
window.removeEventListener("pagehide", onHide);
// Final flush, then mark the feed + playlists stale so resume bars reflect this session on the next
// natural refetch — WITHOUT refetching every loaded feed page on each player close (PERF-2).
void Promise.resolve(persist()).finally(() => {
// Write this session's stored positions INTO the cached lists first. Marking them stale is
// not enough on its own: nothing refetches the feed until it remounts (staleTime 30s,
// refetchOnWindowFocus off), so a reopen used to resume from the position the cache was
// fetched with — and the next checkpoint then persisted that older value over this one.
patchCachedProgress(qc, sessionPos);
qc.invalidateQueries({ queryKey: qk.feed(), refetchType: "none" });
qc.invalidateQueries({ queryKey: qk.playlist(), refetchType: "none" });
});
// Tracked, so a list refetch on the player's close waits for it (`progressFlushes`).
trackProgressFlush(
Promise.resolve(persist()).finally(() => {
// Write this session's stored positions INTO the cached lists first. Marking them stale is
// not enough on its own: nothing refetches the feed until it remounts (staleTime 30s,
// refetchOnWindowFocus off), so a reopen used to resume from the position the cache was
// fetched with — and the next checkpoint then persisted that older value over this one.
patchCachedProgress(qc, sessionPos);
qc.invalidateQueries({ queryKey: qk.feed(), refetchType: "none" });
qc.invalidateQueries({ queryKey: qk.playlist(), refetchType: "none" });
}),
);
const p = playerRef.current;
if (p && typeof p.destroy === "function") {
try {