From d44a61a2b4e6b59ed6bebfe64d6b2f3a1dbb303d Mon Sep 17 00:00:00 2001 From: Adam Soltys Date: Sat, 17 Oct 2026 05:19:51 -0700 Subject: Pager flicks are judged by when the finger moved, not when the move was handled; the counter counts the reachable posts again. Right after a refresh, with clips warming, touch moves queue behind other work and arrive in bursts. The flick's speed was timed on handling, so a burst after a stall read as a fling (a short flick flew) and a stall before the last move as a crawl (a long flick took one step) (Adam, 2026-10-17: "sometimes a quick long flick only proceeds to the next video and doesn't coast as i would expect it to and sometimes the opposite happens -- a short flick does fly"). Each move is now timed by its event's timeStamp, when the finger made it; moves that share one (synthetic ones can) fall back to the handling time. The counter goes back to counting the posts in the carousel (e529ab1 undone): counting the ones still warming confused, since they can't be scrolled to (Adam: "maybe we should go back to showing the total loaded"). tools/pager-swipe-fly-test.js: a gentle swipe whose moves are handled in one burst goes one post; a fast flick whose last move is handled 300 ms late still flies (it took one step with the old timing). pager-ready-gate is back to "the counter grew". pager-ready-gate, pager-tap-slop, pager-seek-hide, pager-first-frame, thread-pager and profile-pager pass. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_013ZDf1cyCQLkfjftYhymZAg --- tools/pager-ready-gate-test.js | 2 +- tools/pager-swipe-fly-test.js | 26 ++++++++++++++++++++++++++ 2 files changed, 27 insertions(+), 1 deletion(-) (limited to 'tools') diff --git a/tools/pager-ready-gate-test.js b/tools/pager-ready-gate-test.js index 4138e42..d6df826 100644 --- a/tools/pager-ready-gate-test.js +++ b/tools/pager-ready-gate-test.js @@ -118,7 +118,7 @@ try { await sleep(300); const g = await groups(); check('…joining above the clip on screen without moving it', g.find((x) => x.cur)?.clip === 'tall3' && Math.abs(g.find((x) => x.cur).top) <= 1, JSON.stringify(g.map((x) => x.clip + ':' + x.top + (x.cur ? '*' : '')))); - check('…and the counter counted it all along (3 / 4 before and after)', n0 === '3 / 4' && (await counter()) === '3 / 4', n0 + ' → ' + (await counter())); + check('…and the counter grew', (await counter()) !== n0, n0 + ' → ' + (await counter())); 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())); diff --git a/tools/pager-swipe-fly-test.js b/tools/pager-swipe-fly-test.js index aec8fdd..cae3536 100644 --- a/tools/pager-swipe-fly-test.js +++ b/tools/pager-swipe-fly-test.js @@ -58,6 +58,23 @@ const swipe = async (px, ms, steps = 6) => { fire('touchend', 700 - px, true); }, [px, ms, steps]); }; +// A swipe whose moves are made on schedule (each event stamped as the finger +// moved) but handled late, the main thread busy: held back and delivered in +// a burst at the end (burst), or the last move and the lift after a stall. +const queued = (px, ms, steps, mode) => page.evaluate(([px, ms, steps, mode]) => { + 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 make = (type, y, up) => new TouchEvent(type, { bubbles: true, cancelable: true, touches: up ? [] : [touch(y)], changedTouches: [touch(y)], targetTouches: up ? [] : [touch(y)] }); + const t0 = performance.now(), evs = [make('touchstart', 700)]; + for (let i = 1; i <= steps; i++) { while (performance.now() < t0 + ms * i / steps); evs.push(make('touchmove', 700 - px * i / steps)); } + evs.push(make('touchend', 700 - px, true)); + if (mode === 'burst') { for (const e of evs) target.dispatchEvent(e); return; } + // stall: all but the last move handled as they come, then 300 ms busy + const tail = evs.splice(-2); + for (const e of evs) target.dispatchEvent(e); + const t1 = performance.now(); while (performance.now() < t1 + 300); + for (const e of tail) target.dispatchEvent(e); +}, [px, ms, steps, mode]); try { await page.setViewport({ width: 390, height: 844, hasTouch: true, isMobile: true }); await page.goto(server.url.href, { waitUntil: 'domcontentloaded' }); @@ -111,6 +128,15 @@ try { await swipe(320, 130); await settled(); check('a gentle swipe from the first: the second, as ever', (await at()) === 2, String(await at())); + console.log('\n[a busy main thread: the speed is the finger\'s, not the handling\'s]'); + // (the dispatch loop runs all the events in one go: built on schedule, each stamped as it was made) + while ((await at()) !== 1) { await page.keyboard.press('ArrowUp'); await settled(); } + await queued(200, 200, 8, 'burst'); await settled(); + check('from post 1, a gentle 200px swipe over 200ms, its moves handled in one burst: one post on', (await at()) === 2, String(await at())); + await queued(600, 80, 4, 'stall'); await settled(); + check('a fast flick whose last move was handled 300ms late still flies', typeof (await at()) === 'number' && (await at()) >= 5, String(await at())); + while ((await at()) !== 1) { await page.keyboard.press('ArrowUp'); await settled(); } + console.log('\n[a finger down mid-flight catches it]'); await swipe(600, 80, 3); await sleep(120); -- cgit v1.2.3