From 2275f1e2541a8592f65f267e6179805556ec473f Mon Sep 17 00:00:00 2001 From: KrX3D Date: Sat, 29 Aug 2026 22:53:14 +0200 Subject: [PATCH] fix: stop the XHR seed path from pre-empting the seeded collection MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The seeding added in this PR never ran. On-device the log shows xhr_seed_triggered present and native_settled entirely absent: 20:49:47.914 keep-one.register (initial) 20:49:48.3-.7 three native continuations 20:49:48.883 batch_fetched (unseeded, from the seed path) ...no reload... 20:49:54.979 continuation.detected -> from_native -> reload Two faults, both mine: 1. The XHR seed path fires on the FIRST continuation request, roughly 400ms in and well before the 1200ms settle timer. It sets __ttPrefetchStarted, so when the timer did fire autoStartCollect bailed at its own guard and the seeded run never happened — collection restarted from batch 2 exactly as before the change. It now stands down while the settle timer is armed, since a run started there has strictly worse information. 2. That path's completion only ever called _triggerReveal, never the cache/reload. So it could finish a complete collection and still leave the page alone — which is what was seen: batches loaded, no reload, and the page only corrected itself six seconds later when a manual scroll produced the final native batch. It now does the same storeFullPlaylist + maybeReloadForFullPlaylist as the scheduled path, so whichever path runs, the reload happens. Also clears __ttNativeSettleTimer as the timer fires. The seed path reads a set handle as "the scheduler owns this", and setTimeout handles do not self-clear, so it would have deferred forever and lost its fallback role. --- mods/features/playlistBatchCollect.js | 28 +++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/mods/features/playlistBatchCollect.js b/mods/features/playlistBatchCollect.js index 60bcf46c..afb54b2f 100644 --- a/mods/features/playlistBatchCollect.js +++ b/mods/features/playlistBatchCollect.js @@ -573,6 +573,10 @@ export function scheduleCollectAfterNativeSettles(continuations, reason) { clearTimeout(window.__ttNativeSettleTimer); window.__ttNativeSettleTimer = setTimeout(() => { + // Clear the handle as the timer fires: the XHR seed path treats a set + // handle as "scheduler owns this" and would otherwise defer forever, + // losing its fallback role if the scheduled run cannot start. + window.__ttNativeSettleTimer = null; if (playlistKeyFromHash() !== key) return; const acc = window.__ttContinuationAcc; const seed = (acc && acc.key === key && Array.isArray(acc.contents)) ? acc.contents : []; @@ -748,6 +752,18 @@ if (typeof XMLHttpRequest !== 'undefined') { return _origXHRSend.apply(this, arguments); } + // Stand down if the settle-scheduler is armed. This path fires on the + // FIRST continuation request (~400ms in), well before the settle timer, + // and it sets __ttPrefetchStarted — which then makes autoStartCollect + // bail at its own guard, so the seeded run never happens and collection + // restarts from batch 2 as if none of this existed. Confirmed on-device: + // xhr_seed_triggered present, native_settled absent, and the collector + // refetching batches YouTube had already delivered. + if (window.__ttNativeSettleTimer) { + _log('playlist.batch_collect.xhr_seed_deferred', { reason: 'settle_scheduled' }); + return _origXHRSend.apply(this, arguments); + } + _log('playlist.batch_collect.xhr_seed_triggered', { url: String(url) }); // CRITICAL: set __ttPrefetchStarted SYNCHRONOUSLY before send(), so that @@ -758,6 +774,7 @@ if (typeof XMLHttpRequest !== 'undefined') { window.__ttPrefetchStarted = true; const startHash = String(window.location?.hash || ''); + const seedKey = playlistKeyFromHash(); const seedUrl = url; const context = reqBody.context; // Use this specific request's own headers (not the module-level @@ -837,6 +854,17 @@ if (typeof XMLHttpRequest !== 'undefined') { items: collected.allContents.length, hasMore: !!collected.continuations, }); + // Same completion handling as the scheduled path. Without this the + // seed path could finish a full collection and still never reload, + // which is exactly what was observed: batches loaded, no reload, and + // the page only corrected itself once a manual scroll produced the + // final native batch. + if (!collected.continuations && !collected.aborted) { + if (storeFullPlaylist(seedKey, collected.allContents)) { + maybeReloadForFullPlaylist(seedKey); + return; + } + } _triggerReveal('playlist.batch_collect.seed_reveal'); })(); });