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
@@ -14,7 +14,7 @@ import { renderHook } from "@testing-library/react";
import { QueryClient } from "@tanstack/react-query";
import { createRef, type RefObject } from "react";
import { useYtProgress } from "./playerModalHooks";
import { patchCachedProgress } from "./progressCache";
import { patchCachedProgress, progressFlushes } from "./progressCache";
import { qk } from "./queryKeys";
import { api, type Video } from "./api";
import type { YTPlayer } from "./youtube";
@@ -113,6 +113,27 @@ describe("useYtProgress — the checkpoint reaches the cache", () => {
expect(cachedPos(qc)).toBe(830);
});
it("hands its closing checkpoint out until it has landed and been patched in", async () => {
// A list refetched on the player's close waits for it: read before it landed, the list held the
// older position and, landing after the patch, put it back.
const qc = new QueryClient();
seedFeed(qc, video());
atPosition(830, 3118);
let land: (v: unknown) => void = () => {};
vi.spyOn(api, "saveProgress").mockReturnValue(new Promise((r) => (land = r)) as never);
const { unmount } = mountPlayer(qc, new Map());
await settle();
unmount();
const flushes = progressFlushes();
expect(flushes).toHaveLength(1);
land({ video_id: VID, position_seconds: 830 });
await flushes[0];
expect(cachedPos(qc)).toBe(830);
await settle();
expect(progressFlushes()).toHaveLength(0);
});
it("mirrors what the SERVER stored, not what the player read", async () => {
const qc = new QueryClient();
seedFeed(qc, video({ position_seconds: 748 }));