mirror of
https://github.com/block/buzz.git
synced 2026-11-01 15:01:12 +00:00
fix(desktop): settle channel opens at latest message
Wait for the virtualized timeline API before consuming initial channel positioning, and do not mistake measurement-driven offsets for reader input while the virtualizer is settling at the floor. Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@sprout-oss.stage.blox.sqprod.co> Signed-off-by: Wes <wesbillman@users.noreply.github.com>
This commit is contained in:
@@ -670,7 +670,12 @@ function VirtualizedTimelineRows({
|
||||
if (!list || !(scroller instanceof HTMLDivElement)) return;
|
||||
onVirtualizerRangeChanged?.();
|
||||
const distanceFromBottom = list.scrollSize - list.viewportSize - offset;
|
||||
if (distanceFromBottom > 32) cancelBottomSettle();
|
||||
// Do not infer reader intent from an intermediate virtualizer offset.
|
||||
// Initial channel positioning deliberately chases the floor while rows
|
||||
// are measured; those measurements can briefly report a large gap and
|
||||
// emit `onScroll` without any user input. Cancelling here strands the
|
||||
// channel above its newest message. The settle hook's wheel, pointer,
|
||||
// touch, and key listeners are the authoritative user-interaction gate.
|
||||
onAtBottomStateChange?.(distanceFromBottom <= 32);
|
||||
if (
|
||||
prependAnchorRef.current !== null ||
|
||||
@@ -686,7 +691,6 @@ function VirtualizedTimelineRows({
|
||||
},
|
||||
[
|
||||
armUpwardMomentum,
|
||||
cancelBottomSettle,
|
||||
capturePrependAnchor,
|
||||
onAtBottomStateChange,
|
||||
onStartReached,
|
||||
|
||||
@@ -607,6 +607,11 @@ export function useAnchoredScroll({
|
||||
// to the requested target message, or to the bottom by default.
|
||||
if (!hasInitializedRef.current) {
|
||||
if (isLoading) return;
|
||||
// The virtualized list owns the actual scroll node. Its API registers in
|
||||
// a child layout effect, after this parent hook's first pass; treating
|
||||
// that API-less pass as initialized writes to the inert outer wrapper
|
||||
// and permanently consumes the channel's initial bottom pin.
|
||||
if (virtualizerOwnsPrependAnchoring && !virtualScrollToBottom) return;
|
||||
// Establish the initial position before the browser paints. The follow-up
|
||||
// frame is a settling pass for content whose measurements land with the
|
||||
// commit (fonts, deferred rows, media), not the first bottom pin. Keeping
|
||||
|
||||
@@ -68,6 +68,46 @@ async function getMessagePosition(
|
||||
}, messageId);
|
||||
}
|
||||
|
||||
test("channel switch settles at the newest message after virtualized rows measure", async ({
|
||||
page,
|
||||
}) => {
|
||||
await installMockBridge(page);
|
||||
await page.goto("/");
|
||||
await page.waitForFunction(
|
||||
() => typeof window.__BUZZ_E2E_EMIT_MOCK_MESSAGE__ === "function",
|
||||
);
|
||||
|
||||
await page.evaluate(() => {
|
||||
const base = Math.floor(Date.now() / 1000);
|
||||
for (let index = 0; index < 80; index += 1) {
|
||||
window.__BUZZ_E2E_EMIT_MOCK_MESSAGE__?.({
|
||||
channelName: "general",
|
||||
content: `switch-bottom ${index} ${"variable-height ".repeat(
|
||||
index % 7,
|
||||
)}`,
|
||||
createdAt: base + index,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Open another channel first so this exercises a real channel switch, where
|
||||
// the virtualizer API is temporarily null while the keyed list remounts.
|
||||
await page.getByTestId("channel-random").click();
|
||||
await expect(page.getByTestId("chat-title")).toHaveText("random");
|
||||
await page.getByTestId("channel-general").click();
|
||||
await expect(page.getByTestId("chat-title")).toHaveText("general");
|
||||
|
||||
const timeline = page.getByTestId("message-timeline");
|
||||
await expect(timeline).toContainText("switch-bottom 79");
|
||||
await expect
|
||||
.poll(async () => {
|
||||
const metrics = await getTimelineMetrics(page);
|
||||
return metrics.scrollHeight - metrics.clientHeight - metrics.scrollTop;
|
||||
})
|
||||
.toBeLessThanOrEqual(1);
|
||||
await expect(page.getByTestId("message-scroll-to-latest")).toHaveCount(0);
|
||||
});
|
||||
|
||||
test("first channel load paints the first window without waiting for the row-floor top-up", async ({
|
||||
page,
|
||||
}) => {
|
||||
|
||||
Reference in New Issue
Block a user