diff options
| author | Adam Soltys <[email protected]> | 2026-10-16 20:24:22 -0700 |
|---|---|---|
| committer | Adam Soltys <[email protected]> | 2026-10-16 20:24:22 -0700 |
| commit | 5a71aaa4855c7f619a8ff9522754ce22393817a5 (patch) | |
| tree | fc07c42511dab673a8402b61ea9ab02484663b14 /tools | |
| parent | ca3b61ec61e2c2f0141eefd373fade86e9925c85 (diff) | |
| download | coinosv3-5a71aaa4855c7f619a8ff9522754ce22393817a5.tar.gz coinosv3-5a71aaa4855c7f619a8ff9522754ce22393817a5.zip | |
The wheel coasts over a video and on a thread, and a turn back against a coast is a gentler half push that winds up again from the next notch.
A notch with the pointer over a clip fell to the browser's single-notch scroll: video and audio were on the wheel handler's hands-off list from the first commit, though the wheel does nothing to a player itself. The thread page was outside the handler's scope altogether, so a thread scrolled plain while the feed coasted. Both coast now, like the words around them (Adam, 2026-10-16).
A reversal was a full notch the other way followed by two notches without wind-up, and felt like an overshoot. It is now half a notch's travel at half the speed, so the reader who flew past a post settles back onto it; the next notch the same way is a plain one, winding up from whatever speed the half push left, so a spin on after a reversal gets going at once. (The five-notch quarter-size hunt stays gone.)
feed-wheel-reverse-test: the half push, the wind-up from the second notch, a notch over a video / audio / a thread coasts, one in a text box or the pager does not; the fixture's target can sit inside a given element and on a thread page.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_01J1Kon8bxJoHRpJdSzshCkp
Diffstat (limited to 'tools')
| -rw-r--r-- | tools/feed-wheel-reverse-test.js | 57 |
1 files changed, 38 insertions, 19 deletions
diff --git a/tools/feed-wheel-reverse-test.js b/tools/feed-wheel-reverse-test.js index 632bbc6..be5ad54 100644 --- a/tools/feed-wheel-reverse-test.js +++ b/tools/feed-wheel-reverse-test.js @@ -5,7 +5,7 @@ const source = await Bun.file('src/app.js').text(); const start = source.indexOf('(() => {', source.indexOf('// The feed scrolls like a phone')); const end = source.indexOf("\nwindow.addEventListener('load'", start); assert(start > 0 && end > start); -function fixture() { +function fixture(inside = '') { let now = 100, id = 0; const handlers = new Map(), frames = new Map(), steps = []; const window = { scrollY: 5000, @@ -13,7 +13,8 @@ function fixture() { scrollBy: ({ top }) => { steps.push(top); window.scrollY += top; }, }; const document = { body: {}, addEventListener() {}, removeEventListener() {} }; - const target = { closest: (selector) => selector.includes('.feed-page') ? target : null, + // the target sits in a feed page (or, for a thread case, a thread page), inside the given element + const target = { closest: (selector) => selector.split(/,\s*/).some((p) => p === inside || (p === '.feed-page' && inside !== '.thread-page')) ? target : null, scrollHeight: 100, clientHeight: 100, parentElement: document.body }; runInNewContext(source.slice(start, end), { window, document, matchMedia: () => ({ matches: false }), performance: { now: () => now }, @@ -46,29 +47,47 @@ for (const dir of [1, -1]) { assert((f.y - reversed) * dir < 0, 'next frame continues in the new direction'); assert(Math.abs(f.y - reversed) < 20, 'new velocity is not applied to the old frame delay'); settle(f); - // The turn back is a plain notch from rest: its full travel, no flight the other way. + // The turn back is a gentler push than a notch: half a notch's travel, so + // a reader who overshot settles back rather than flying the other way. const back = (before - f.y) * dir; - assert(back > 280 && back < 330, 'a reverse notch against a coast travels one notch: ' + back); - // For a moment after a reversal notches don't wind the speed up: a - // reversal and a quick second notch travel about two notches, not a flight. - let calm; + assert(back > 130 && back < 170, 'a reverse notch against a coast travels half a notch: ' + back); + // The next notch the same way is a plain one again, winding up from the + // half push's speed: a reversal and a quick second notch travel about a + // notch and a half. + let pair; { f.wheel(-dir); f.advance(17); f.frame(); f.wheel(-dir); f.advance(120); f.frame(); // coasting again, the other way - const y = f.y; f.wheel(dir); f.advance(60); f.frame(); f.wheel(dir); settle(f); calm = (f.y - y) * dir; - assert(calm > 540 && calm < 700, 'a reversal and a quick second notch: about two notches of travel: ' + calm); } - // From rest, well after, the wind-up is back: the same two quick notches go further. - { f.advance(2500); const y = f.y; f.wheel(dir); f.advance(60); f.frame(); f.wheel(dir); settle(f); const s = (f.y - y) * dir; assert(s > calm + 40, 'two quick notches with the wind-up go further: ' + s + ' vs ' + calm); } - // A spin that keeps going after a reversal winds up again from its third - // notch: a reversal plus four quick notches goes well past five plain ones. + const y = f.y; f.wheel(dir); f.advance(60); f.frame(); f.wheel(dir); settle(f); pair = (f.y - y) * dir; + assert(pair > 440 && pair < 560, 'a reversal and a quick second notch: about a notch and a half: ' + pair); } + // From rest the same two quick notches go further: a full first push, and the wind-up. + { f.advance(2500); const y = f.y; f.wheel(dir); f.advance(60); f.frame(); f.wheel(dir); settle(f); const s = (f.y - y) * dir; assert(s > pair + 100, 'two quick notches from rest go further: ' + s + ' vs ' + pair); } + // A spin that keeps going after a reversal winds up from its second + // notch: a reversal plus four quick notches goes well past four plain ones. let spin; { f.wheel(-dir); f.advance(17); f.frame(); f.wheel(-dir); f.advance(120); f.frame(); const y = f.y; f.wheel(dir); for (let i = 0; i < 4; i++) { f.advance(60); f.frame(); f.wheel(dir); } settle(f); spin = (f.y - y) * dir; - assert(spin > 5 * 300 + 150, 'a spin on after a reversal winds up again: ' + spin); } - // ...and the same five quick notches from rest go about as far (the two calm ones cost little). + assert(spin > 4 * 300 + 150, 'a spin on after a reversal winds up again: ' + spin); } + // ...and the same five quick notches from rest go about as far (the half push costs little). { f.advance(2500); const y = f.y; f.wheel(dir); for (let i = 0; i < 4; i++) { f.advance(60); f.frame(); f.wheel(dir); } settle(f); const s = (f.y - y) * dir; - assert(s < spin * 1.25, 'five from rest vs five after a reversal: ' + s + ' vs ' + spin); settle(f, 200); } + assert(s < spin * 1.3, 'five from rest vs five after a reversal: ' + s + ' vs ' + spin); settle(f, 200); } // From rest a notch either way is a full push. { f.advance(2500); const y = f.y; f.wheel(dir); settle(f); const s = (f.y - y) * dir; assert(s > 280 && s < 330, 'from rest a notch either way is a full push: ' + s); } - // A turn back against a coast that only just died is a reversal too (caught, no wind-up). - { f.wheel(dir); settle(f, 40); f.advance(100); const y = f.y; f.wheel(-dir); f.advance(60); f.frame(); f.wheel(-dir); settle(f); const s = (y - f.y) * dir; assert(s > 520 && s < 700, 'reversing right after the coast dies: no wind-up: ' + s); } + // A turn back against a coast that only just died is a reversal too: the half push. + { f.wheel(dir); settle(f, 40); f.advance(100); const y = f.y; f.wheel(-dir); settle(f); const s = (y - f.y) * dir; assert(s > 130 && s < 170, 'reversing right after the coast dies is the half push: ' + s); } + // Well after the coast died it is a plain notch from rest, whichever way. + { f.advance(2500); const y = f.y; f.wheel(-dir); settle(f); const s = (y - f.y) * dir; assert(s > 280 && s < 330, 'a notch the other way from rest is a full push: ' + s); } } -console.log('✓ immediate reversal both ways, stale frame timestamps, busy frame recovery, a reversal is a plain notch, two calm notches, then the wind-up is back'); +// A notch over a video (or an audio player) in the feed coasts like any +// other — the wheel does nothing to a player itself — and so does one on a +// thread page. +for (const sel of ['video', 'audio', '.thread-page']) { + const f = fixture(sel); + const y = f.y; f.wheel(1); settle(f); + assert(f.y - y > 280 && f.y - y < 330, 'a notch over ' + sel + ' coasts a full push: ' + (f.y - y)); +} +// ...while a notch in a text box, or in the media pager, is left alone. +for (const sel of ['textarea', '.media-pager']) { + const f = fixture(sel); + const y = f.y; f.wheel(1); settle(f); + assert.equal(f.y, y, 'a notch in ' + sel + ' is not ours'); +} +console.log('✓ immediate reversal both ways, stale frame timestamps, busy frame recovery, a reversal is a half push that winds up again from the next notch, videos and threads coast'); |
