diff --git a/package.json b/package.json index de96fea..4178f38 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "netfelix-audio-fix", - "version": "2026.04.15.3", + "version": "2026.04.15.4", "scripts": { "dev:server": "NODE_ENV=development bun --hot server/index.tsx", "dev:client": "vite", diff --git a/src/features/pipeline/PipelinePage.tsx b/src/features/pipeline/PipelinePage.tsx index fb905f4..39d8041 100644 --- a/src/features/pipeline/PipelinePage.tsx +++ b/src/features/pipeline/PipelinePage.tsx @@ -25,43 +25,50 @@ export function PipelinePage() { const [queueStatus, setQueueStatus] = useState(null); const [loading, setLoading] = useState(true); - const load = useCallback(async () => { - const [pipelineRes, groupsRes] = await Promise.all([ - api.get("/api/review/pipeline"), - api.get("/api/review/groups?offset=0&limit=25"), - ]); + const loadPipeline = useCallback(async () => { + const pipelineRes = await api.get("/api/review/pipeline"); setData(pipelineRes); - setInitialGroups(groupsRes); - setLoading(false); }, []); + const loadReviewGroups = useCallback(async () => { + const groupsRes = await api.get("/api/review/groups?offset=0&limit=25"); + setInitialGroups(groupsRes); + }, []); + + // Full refresh: used on first mount and after user-driven mutations + // (approve/skip). SSE-driven refreshes during a running job call + // loadPipeline only, so the Review column's scroll-loaded pages don't get + // wiped every second by job_update events. + const loadAll = useCallback(async () => { + await Promise.all([loadPipeline(), loadReviewGroups()]); + setLoading(false); + }, [loadPipeline, loadReviewGroups]); + useEffect(() => { - load(); - }, [load]); + loadAll(); + }, [loadAll]); // SSE for live updates. job_update fires on every status change and per-line - // stdout flush of the running job — without coalescing, the pipeline endpoint - // (a 500-row review query + counts) would re-run several times per second. + // stdout flush — coalesce via 1s debounce so the pipeline endpoint doesn't + // re-run several times per second. const reloadTimer = useRef | null>(null); useEffect(() => { - const scheduleReload = () => { + const schedulePipelineReload = () => { if (reloadTimer.current) return; reloadTimer.current = setTimeout(() => { reloadTimer.current = null; - load(); + loadPipeline(); }, 1000); }; const es = new EventSource("/api/execute/events"); es.addEventListener("job_update", (e) => { - // When a job leaves 'running' (done / error / cancelled), drop any - // stale progress so the bar doesn't linger on the next job's card. try { const upd = JSON.parse((e as MessageEvent).data) as { id: number; status: string }; if (upd.status !== "running") setProgress(null); } catch { /* ignore malformed events */ } - scheduleReload(); + schedulePipelineReload(); }); es.addEventListener("job_progress", (e) => { setProgress(JSON.parse((e as MessageEvent).data)); @@ -73,7 +80,7 @@ export function PipelinePage() { es.close(); if (reloadTimer.current) clearTimeout(reloadTimer.current); }; - }, [load]); + }, [loadPipeline]); if (loading || !data || !initialGroups) return
Loading pipeline...
; @@ -88,11 +95,11 @@ export function PipelinePage() { initialResponse={initialGroups} totalItems={data.reviewItemsTotal} jellyfinUrl={data.jellyfinUrl} - onMutate={load} + onMutate={loadAll} /> - - - + + + );