From 01303c8e920d34f62f57de8dd9d6ed03f0543419 Mon Sep 17 00:00:00 2001 From: LukeParkerDev <10430890+Hona@users.noreply.github.com> Date: Sun, 14 Jun 2026 21:38:17 +0200 Subject: [PATCH] fix(app): preserve scrolling during history loads --- packages/app/e2e/smoke/session-timeline.spec.ts | 4 ++++ .../app/src/pages/session/message-timeline.tsx | 16 ++++++++++++---- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/packages/app/e2e/smoke/session-timeline.spec.ts b/packages/app/e2e/smoke/session-timeline.spec.ts index 8fef4f7ba34..526e978151b 100644 --- a/packages/app/e2e/smoke/session-timeline.spec.ts +++ b/packages/app/e2e/smoke/session-timeline.spec.ts @@ -54,6 +54,10 @@ test.describe("smoke: session timeline", () => { await page.waitForTimeout(20) } expect(requests.some((request) => request.before && request.phase === "end")).toBe(false) + for (let index = 0; index < 12; index++) { + await page.mouse.wheel(0, -120) + await page.waitForTimeout(20) + } const keys = ["prt_user_text_smoke_0032", "prt_text_2_smoke_0032", "prt_tool_apply_patch_8_smoke_0032"] const positions = () => scroller.evaluate((element, keys) => { diff --git a/packages/app/src/pages/session/message-timeline.tsx b/packages/app/src/pages/session/message-timeline.tsx index 6761df829d6..2d78cf747dd 100644 --- a/packages/app/src/pages/session/message-timeline.tsx +++ b/packages/app/src/pages/session/message-timeline.tsx @@ -406,13 +406,19 @@ export function MessageTimeline(props: { let prependAnchor: { key: string; offset: number } | undefined let prependAnchorFrame: number | undefined + let prependLoading = false const clearPrependAnchor = () => { + prependLoading = false prependAnchor = undefined if (prependAnchorFrame === undefined) return cancelAnimationFrame(prependAnchorFrame) prependAnchorFrame = undefined } const capturePrependAnchor = () => { + prependLoading = true + updatePrependAnchor() + } + const updatePrependAnchor = () => { const root = listRoot() if (!root) return const view = root.getBoundingClientRect() @@ -425,6 +431,7 @@ export function MessageTimeline(props: { prependAnchor = { key: anchor.element.dataset.timelineKey, offset: anchor.rect.top - view.top } } const restorePrependAnchor = () => { + prependLoading = false applyPrependAnchor() } const applyPrependAnchor = () => { @@ -479,7 +486,7 @@ export function MessageTimeline(props: { anchorTo: "end", followOnAppend: true, scrollEndThreshold: 80, - overscan: 40, + overscan: 50, paddingEnd: 64, rangeExtractor: (range) => { const id = activeMessageID() @@ -592,7 +599,7 @@ export function MessageTimeline(props: { const handleListWheel = (event: WheelEvent & { currentTarget: HTMLDivElement }) => { - clearPrependAnchor() + if (!prependLoading) clearPrependAnchor() const root = event.currentTarget const delta = normalizeWheelDelta({ deltaY: event.deltaY, @@ -604,7 +611,7 @@ export function MessageTimeline(props: { } const handleListTouchStart = (event: TouchEvent) => { - clearPrependAnchor() + if (!prependLoading) clearPrependAnchor() touchGesture = event.touches[0]?.clientY } @@ -630,12 +637,13 @@ export function MessageTimeline(props: { } const handleListPointerDown = (event: PointerEvent & { currentTarget: HTMLDivElement }) => { - clearPrependAnchor() + if (!prependLoading) clearPrependAnchor() if (event.target !== event.currentTarget) return props.onMarkScrollGesture(event.currentTarget) } const handleListScroll = (event: Event & { currentTarget: HTMLDivElement }) => { + if (prependLoading) updatePrependAnchor() props.onScheduleScrollState(event.currentTarget) props.onHistoryScroll() if (!props.hasScrollGesture()) return