From 0f77fc3598bcef7b68f7e866b82eaa1b0322e6eb Mon Sep 17 00:00:00 2001 From: iris Date: Thu, 4 Jun 2026 19:48:18 +0200 Subject: [PATCH] fix(#1297): prevent scroll jump on loadMore prepend MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two cooperating fixes: 1. overflow-anchor: none on .live — disables browser scroll anchoring so the manual scrollTop compensation in loadMore() is the sole mechanism. Without this the browser auto-adjusts scrollTop when rows are prepended above the viewport, then our += delta doubles the compensation, causing the erratic jump. 2. updateLoadMoreBtn() moved before the beforeH baseline capture — if histHasMore becomes false the load-more button is removed here rather than after the delta is applied, so the button height is already baked into beforeH and doesn't shift the viewport post- compensation. --- frontend/packages/shared/src/terminal.css | 5 +++++ frontend/packages/shared/src/terminal.js | 9 ++++++++- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/frontend/packages/shared/src/terminal.css b/frontend/packages/shared/src/terminal.css index d7cc5b64..6a39682c 100644 --- a/frontend/packages/shared/src/terminal.css +++ b/frontend/packages/shared/src/terminal.css @@ -36,6 +36,11 @@ overflow-y: auto; max-height: 32em; font-family: inherit; + /* Disable browser scroll anchoring — the terminal manages scroll + position manually (snapToBottom + loadMore compensation). Scroll + anchoring would double-compensate scrollTop during loadMore() + prepends, causing an erratic jump. */ + overflow-anchor: none; } .live.terminal { background: transparent; diff --git a/frontend/packages/shared/src/terminal.js b/frontend/packages/shared/src/terminal.js index 0f495fa7..aefb0223 100644 --- a/frontend/packages/shared/src/terminal.js +++ b/frontend/packages/shared/src/terminal.js @@ -316,6 +316,12 @@ export function create(opts) { histHasMore = body.has_more || false; if (typeof body.min_id === 'number') histMinId = body.min_id; + // Resolve load-more button state before capturing the scroll + // baseline so that any button removal is already reflected in + // beforeH — otherwise the button's height would be missing from + // the delta and the viewport would drift up by that amount. + updateLoadMoreBtn(); + if (events.length > 0) { // Render into a detached element; `log` is temporarily redirected // so that row/details/etc. append there instead of rootLog. @@ -338,9 +344,10 @@ export function create(opts) { const beforeH = rootLog.scrollHeight; while (tempEl.firstChild) rootLog.insertBefore(tempEl.firstChild, anchor); // Compensate scroll so the viewport stays on the same content. + // overflow-anchor: none on .live ensures the browser does not + // also auto-adjust scrollTop (which would double the delta). rootLog.scrollTop += rootLog.scrollHeight - beforeH; } - updateLoadMoreBtn(); } catch (err) { console.warn('loadMore failed', err); updateLoadMoreBtn();