From 47dd7551df920600a63e08b3b15aeb4cd4d3406f Mon Sep 17 00:00:00 2001 From: Adam Soltys Date: Sat, 17 Oct 2026 05:27:36 -0700 Subject: The video pager flies only once ten clips are ready to play; until then a flick or a spin of the wheel is one post at a time. Adam, 2026-10-17: "let's not allow flying/coasting until we have at least 10 videos fully warmed up". Right after a refresh a fling outran the warming, landing on whatever was ready while clips joined around it. Ready is in the carousel: the clip's first frame on its slide and its opening cached. Picture pagers and Data Saver are unchanged. tools/pager-ready-gate-test.js: with four clips ready a hard flick goes one post (it came round to the first with the gate off). pager-swipe-fly and pager-wheel pass; feed-pager-ahead fails the same two picture checks as before. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_013ZDf1cyCQLkfjftYhymZAg --- src/features/messages.js | 11 +++++++++-- tools/pager-ready-gate-test.js | 17 +++++++++++++++++ 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/src/features/messages.js b/src/features/messages.js index cf6e2c7..654b6d5 100644 --- a/src/features/messages.js +++ b/src/features/messages.js @@ -13139,6 +13139,13 @@ export function messagesFeature(ctx) { // it is passing and steps back from there; a touch or a key catches it // too (pagerHold → onDone). A notch from rest is the single step it was. let fly = null; // { to: the post flown to, n: notches in this run, at, dir } + // A video pager flies only once ten clips are ready to play (in the + // carousel): before that a fling outran the warming, landing on whatever + // was ready and fighting the clips joining (Adam, 2026-10-17: "let's not + // allow flying/coasting until we have at least 10 videos fully warmed + // up"). Until then a flick or a spin of the wheel is one post at a time. + const PAGER_FLY_READY = 10; + const pagerCanFly = () => !pager || pager.kind !== 'videos' || frugalData() || pagerGroups().length >= PAGER_FLY_READY; function pagerFly(dir, now) { if (!pager) return; const stops = pagerGroups(); @@ -13153,7 +13160,7 @@ export function messagesFeature(ctx) { const here = pagerHere(); from = here && here.closest('.mp-group'); } - const next = stops.indexOf(from) + dir * (n <= 2 ? 1 : 2); + const next = stops.indexOf(from) + dir * (n <= 2 || !pagerCanFly() ? 1 : 2); const i = (next % stops.length + stops.length) % stops.length; if (next < 0 || next >= stops.length) { fly = null; pagerWrap(stops[i]); return; } const to = stops[i]; @@ -13339,7 +13346,7 @@ export function messagesFeature(ctx) { if (!stops.length) return; // the stop it set off from, then one along (or back) — or, flung, several const from = stops.reduce((bi, p, i) => (Math.abs(p - d.start) < Math.abs(stops[bi] - d.start) ? i : bi), 0); - const posts = y && step && Math.abs(vel) >= FLY_V ? Math.min(FLY_MAX, Math.max(1, Math.round(Math.abs(vel) * FLY_TAU / Math.max(1, box.clientHeight)))) : 1; + const posts = y && step && Math.abs(vel) >= FLY_V && pagerCanFly() ? Math.min(FLY_MAX, Math.max(1, Math.round(Math.abs(vel) * FLY_TAU / Math.max(1, box.clientHeight)))) : 1; const next = from + step * posts; // Past either end, round to the other: the first post (going up, the // last) — a step, or a fling whose coast is mostly beyond the end. A diff --git a/tools/pager-ready-gate-test.js b/tools/pager-ready-gate-test.js index d6df826..613f7d5 100644 --- a/tools/pager-ready-gate-test.js +++ b/tools/pager-ready-gate-test.js @@ -122,6 +122,23 @@ try { await page.keyboard.press('ArrowUp'); check('a step back up lands on the one that joined', await waitFor(() => { const s = document.querySelector('.mp-slide.mp-cur'); return !!s && /tall2/.test(s.getAttribute('data-k')) && !s.querySelector('video').paused; }, null, 6000), String(await curClip())); + console.log('\n[no flying with fewer than ten clips ready]'); + await sleep(500); + const ready = (await inCarousel()).split(' ').length; + // a hard flick (600px in 80ms): a flight's worth, were flying allowed + await page.evaluate(() => { + const el = document.querySelector('.mp-scroll'), target = document.elementFromPoint(195, 700) || el; + const touch = (y) => new Touch({ identifier: 1, target, clientX: 195, clientY: y, pageX: 195, pageY: y }); + const fire = (type, y, up) => target.dispatchEvent(new TouchEvent(type, { bubbles: true, cancelable: true, touches: up ? [] : [touch(y)], changedTouches: [touch(y)], targetTouches: up ? [] : [touch(y)] })); + const t0 = performance.now(); + fire('touchstart', 700); + for (let i = 1; i <= 3; i++) { while (performance.now() < t0 + 80 * i / 3); fire('touchmove', 700 - 600 * i / 3); } + fire('touchend', 100, true); + }); + check('with ' + ready + ' clips ready, a hard flick from tall2 goes one post, to tall3', ready < 10 && await waitFor(() => { const s = document.querySelector('.mp-slide.mp-cur'); return !!s && /tall3/.test(s.getAttribute('data-k')); }, null, 4000), String(await curClip())); + await page.keyboard.press('ArrowUp'); + await waitFor(() => /tall2/.test(document.querySelector('.mp-slide.mp-cur')?.getAttribute('data-k') || ''), null, 4000); + console.log('\n[the toggle in the pager]'); check('the pager\'s toggle says tall only', (await page.evaluate(() => document.querySelector('.mp-fit')?.textContent)) === 'Tall only'); await page.evaluate(() => document.querySelector('.mp-fit').click()); -- cgit v1.2.3