mirror of
https://github.com/block/buzz.git
synced 2026-08-18 06:50:31 +02:00
fix(desktop): retire prepend mode on every reader wheel (#2913)
## Summary - retire Virtua prepend reconciliation on every ordinary reader wheel event, including events suppressed from its separate wheel-timing heuristic - keep Ctrl+wheel browser zoom excluded from reader ownership - arm the Ctrl+wheel prepend probe before pagination begins so it cannot miss the commit - preserve ESM/CJS patch parity and update the patch lock hash ## Why main was red PR #2855 added the reader-wheel retirement action after Virtua's existing suppression guard. Once the first event set that guard, later events in the same wheel burst returned before retiring prepend mode. A late ResizeObserver correction could then pull the viewport backward by 20–40px. The same test had failed twice on #2855 but passed its final retry, so the PR job appeared green; the merge commit lost all three retries. The separate Ctrl+wheel failure was a test race: its MutationObserver was registered after the request had already been triggered and could miss the prepend commit. ## Validation - desktop full unit suite: 3,515 passed - pre-push desktop check: passed - `git diff --check`: passed - CI is the E2E verification; no local E2E was run Fixes the main-branch failure in CI run 30180099007. Signed-off-by: Wes <wesbillman@users.noreply.github.com> Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz>
This commit is contained in:
@@ -21,7 +21,17 @@ test("reader wheel retires Virtua shift mode without publishing scroll end", ()
|
||||
.filter(Boolean),
|
||||
);
|
||||
assert.deepEqual(addedActionBodies, [["I = 0;"], ["w = 0;"]]);
|
||||
assert.match(patch, /\+\s+e\.q\(9\);/);
|
||||
assert.match(patch, /\+\s+e\.B\(9\);/);
|
||||
assert.match(
|
||||
patch,
|
||||
/if \(!e\.M\(\) \|\| t\.ctrlKey\) return;\n\+\s+e\.q\(9\);\n\+\s+if \(f\) return;/,
|
||||
);
|
||||
assert.match(
|
||||
patch,
|
||||
/if \(!e\.M\(\) \|\| t\.ctrlKey\) return;\n\+\s+e\.B\(9\);\n\+\s+if \(c\) return;/,
|
||||
);
|
||||
assert.doesNotMatch(
|
||||
patch,
|
||||
/\+\s+if \((?:f|c) \|\| !e\.M\(\) \|\| t\.ctrlKey\) return;/,
|
||||
);
|
||||
assert.doesNotMatch(patch, /\+\s+e\.(?:q|B)\(2\);/);
|
||||
});
|
||||
|
||||
@@ -252,6 +252,29 @@ test.describe("list virtualization", () => {
|
||||
});
|
||||
await page.waitForTimeout(150);
|
||||
const before = await sampleVisibleAnchor();
|
||||
const ctrlWheelPromise =
|
||||
pageIndex === 0
|
||||
? timeline.evaluate(
|
||||
(scroller) =>
|
||||
new Promise<boolean>((resolve) => {
|
||||
const s = scroller as HTMLElement;
|
||||
const observer = new MutationObserver(() => {
|
||||
observer.disconnect();
|
||||
// Ctrl+wheel is browser zoom, not reader scroll intent. Fire
|
||||
// it synchronously with the prepend DOM commit, before the
|
||||
// ResizeObserver measurement batch reconciles estimated rows.
|
||||
s.dispatchEvent(
|
||||
new WheelEvent("wheel", { ctrlKey: true, deltaY: -100 }),
|
||||
);
|
||||
resolve(true);
|
||||
});
|
||||
observer.observe(s.firstElementChild ?? s, {
|
||||
childList: true,
|
||||
subtree: true,
|
||||
});
|
||||
}),
|
||||
)
|
||||
: Promise.resolve(false);
|
||||
const wheelTracePromise = timeline.evaluate(async (scroller) => {
|
||||
const s = scroller as HTMLElement;
|
||||
let previousScrollTop = s.scrollTop;
|
||||
@@ -297,25 +320,8 @@ test.describe("list virtualization", () => {
|
||||
});
|
||||
const committedAnchor = await sampleVisibleAnchor(before.id);
|
||||
const motion = await timeline.evaluate(
|
||||
async (scroller, { anchorId, anchorTop, oldHeight, testCtrlWheel }) => {
|
||||
async (scroller, { anchorId, anchorTop, oldHeight }) => {
|
||||
const s = scroller as HTMLElement;
|
||||
let ctrlWheelDispatched = false;
|
||||
const mutationObserver = testCtrlWheel
|
||||
? new MutationObserver(() => {
|
||||
if (ctrlWheelDispatched) return;
|
||||
ctrlWheelDispatched = true;
|
||||
// Ctrl+wheel is browser zoom, not reader scroll intent. Fire it
|
||||
// synchronously with the prepend DOM commit, before the
|
||||
// ResizeObserver measurement batch reconciles estimated rows.
|
||||
s.dispatchEvent(
|
||||
new WheelEvent("wheel", { ctrlKey: true, deltaY: -100 }),
|
||||
);
|
||||
})
|
||||
: null;
|
||||
mutationObserver?.observe(s.firstElementChild ?? s, {
|
||||
childList: true,
|
||||
subtree: true,
|
||||
});
|
||||
let maxDrift = 0;
|
||||
let sawPrepend = false;
|
||||
let sawAnchorAfterPrepend = false;
|
||||
@@ -343,20 +349,16 @@ test.describe("list virtualization", () => {
|
||||
if (sawAnchorAfterPrepend && stableFrames >= 8) break;
|
||||
await new Promise((resolve) => requestAnimationFrame(resolve));
|
||||
}
|
||||
mutationObserver?.disconnect();
|
||||
return { ctrlWheelDispatched, maxDrift, sawPrepend };
|
||||
return { maxDrift, sawPrepend };
|
||||
},
|
||||
{
|
||||
anchorId: committedAnchor.id,
|
||||
anchorTop: committedAnchor.top,
|
||||
oldHeight: before.scrollHeight,
|
||||
// Exercise browser-zoom input once while a real prepend transaction
|
||||
// still owns its measurement reconciliation.
|
||||
testCtrlWheel: pageIndex === 0,
|
||||
},
|
||||
);
|
||||
expect(motion.sawPrepend).toBe(true);
|
||||
if (pageIndex === 0) expect(motion.ctrlWheelDispatched).toBe(true);
|
||||
if (pageIndex === 0) expect(await ctrlWheelPromise).toBe(true);
|
||||
expect(motion.maxDrift).toBeLessThan(5);
|
||||
|
||||
await expect
|
||||
|
||||
@@ -54,11 +54,13 @@ index e02dfd0b3db60faff2cc705d7ce0aa51cb958f5a..b12677a356f748ebccf6e16cb781efe1
|
||||
}
|
||||
l && (d = 1 + (2147483647 & d), o && z && ($ += z, z = 0), O.forEach(([e, t]) => {
|
||||
l & e && t(n);
|
||||
@@ -186,6 +198,7 @@ const r = null, {min: n, max: s, abs: i, floor: l} = Math, c = (e, t, o) => n(o,
|
||||
@@ -186,6 +198,8 @@ const r = null, {min: n, max: s, abs: i, floor: l} = Math, c = (e, t, o) => n(o,
|
||||
c = l(), d && (a = !0), i && e.q(6, i()), e.q(1, n()), h();
|
||||
}, p = t => {
|
||||
if (f || !e.M() || t.ctrlKey) return;
|
||||
- if (f || !e.M() || t.ctrlKey) return;
|
||||
+ if (!e.M() || t.ctrlKey) return;
|
||||
+ e.q(9);
|
||||
+ if (f) return;
|
||||
const r = l() - c;
|
||||
150 > r && 50 < r && (o ? t.deltaX : t.deltaY) && (f = !0);
|
||||
}, v = () => {
|
||||
@@ -150,11 +152,13 @@ index 110ac3858a002a6cdb698da2b56350bc1bf609d2..db83efc83fb3b6d7aa07d83b7463d6ed
|
||||
}
|
||||
d && (i = 1 + (2147483647 & i), o && p && (g += p, p = 0), O.forEach(([e, t]) => {
|
||||
d & e && t(n);
|
||||
@@ -190,6 +202,7 @@ const u = null, {min: d, max: a, abs: h, floor: g} = Math, p = (e, t, o) => d(o,
|
||||
@@ -190,6 +202,8 @@ const u = null, {min: d, max: a, abs: h, floor: g} = Math, p = (e, t, o) => d(o,
|
||||
l = i(), d && (a = !0), s && e.B(6, s()), e.B(1, n()), h();
|
||||
}, p = t => {
|
||||
if (c || !e.M() || t.ctrlKey) return;
|
||||
- if (c || !e.M() || t.ctrlKey) return;
|
||||
+ if (!e.M() || t.ctrlKey) return;
|
||||
+ e.B(9);
|
||||
+ if (c) return;
|
||||
const n = i() - l;
|
||||
150 > n && 50 < n && (o ? t.deltaX : t.deltaY) && (c = !0);
|
||||
}, v = () => {
|
||||
|
||||
Generated
+3
-3
@@ -9,7 +9,7 @@ overrides:
|
||||
|
||||
patchedDependencies:
|
||||
isomorphic-git: e9b414a60d4cf1d8aa18f7a779483984e821989967c235662566e94ef0238d3f
|
||||
virtua@0.49.3: 63923c1f0c73f6fd487c788159fe2f1bd6930e5aeebe815cc511c1c4747294bc
|
||||
virtua@0.49.3: 30a7a92b94800ec37be8d36546ea98c04a05dcd0637f9fac27a06f016ba2eff4
|
||||
|
||||
importers:
|
||||
|
||||
@@ -232,7 +232,7 @@ importers:
|
||||
version: 2.1.0
|
||||
virtua:
|
||||
specifier: 0.49.3
|
||||
version: 0.49.3(patch_hash=63923c1f0c73f6fd487c788159fe2f1bd6930e5aeebe815cc511c1c4747294bc)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)
|
||||
version: 0.49.3(patch_hash=30a7a92b94800ec37be8d36546ea98c04a05dcd0637f9fac27a06f016ba2eff4)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)
|
||||
yaml:
|
||||
specifier: ^2.8.3
|
||||
version: 2.9.0
|
||||
@@ -6926,7 +6926,7 @@ snapshots:
|
||||
'@types/unist': 3.0.3
|
||||
vfile-message: 4.0.3
|
||||
|
||||
virtua@0.49.3(patch_hash=63923c1f0c73f6fd487c788159fe2f1bd6930e5aeebe815cc511c1c4747294bc)(react-dom@19.2.7(react@19.2.7))(react@19.2.7):
|
||||
virtua@0.49.3(patch_hash=30a7a92b94800ec37be8d36546ea98c04a05dcd0637f9fac27a06f016ba2eff4)(react-dom@19.2.7(react@19.2.7))(react@19.2.7):
|
||||
optionalDependencies:
|
||||
react: 19.2.7
|
||||
react-dom: 19.2.7(react@19.2.7)
|
||||
|
||||
Reference in New Issue
Block a user