Skip to content

Fix: Terminal scrolls to top after tab switch (Issue #560)#561

Merged
waleedkadous merged 1 commit intomainfrom
builder/bugfix-560-terminal-still-scrolls-to-top-
Feb 25, 2026
Merged

Fix: Terminal scrolls to top after tab switch (Issue #560)#561
waleedkadous merged 1 commit intomainfrom
builder/bugfix-560-terminal-still-scrolls-to-top-

Conversation

@waleedkadous
Copy link
Contributor

Summary

Fixes #560

  • Root cause: xterm's buffer.active.viewportY (backed by internal ydisp) becomes stale when the terminal container is hidden via display:none during tab switches or panel collapse. The browser resets the viewport element's scrollTop to 0, and xterm syncs ydisp to 0. When safeFit() later reads viewportY=0, it preserves this incorrect position at the top of the buffer.

  • Fix: Track scroll state (viewportY, baseY, wasAtBottom) externally in JS variables updated via term.onScroll(). These variables are immune to DOM state changes from display toggling. safeFit() now reads from the tracked state instead of xterm buffer.

  • Tests: Rewrote Terminal.fit-scroll.test.tsx with 7 tests (previously all 4 were skipped due to jsdom getBoundingClientRect returning 0x0). Three new tests specifically cover the Issue Terminal still scrolls to top of buffer (regression from #423) #560 scenario. Added onScroll mock to all 9 other terminal test files.

Changes

File What
Terminal.tsx Added external scroll state tracking via term.onScroll(), modified safeFit() to use tracked state, synced tracked state after replay flush
Terminal.fit-scroll.test.tsx Rewrote with 7 working tests including Issue #560 regression tests
9 other test files Added onScroll mock to MockTerminal class

Test plan

  • All 175 dashboard unit tests pass (22 test files)
  • TypeScript type check passes (tsc --noEmit)
  • Playwright E2E: Open multiple terminal tabs, scroll up in one, switch tabs, switch back — scroll position preserved
  • Playwright E2E: Collapse and expand split pane — scroll position preserved

…one toggling

xterm's buffer.active.viewportY (backed by ydisp) becomes stale when the
terminal container is hidden via display:none during tab switches or panel
collapse. The browser resets the viewport scrollTop to 0, and xterm syncs
ydisp to 0. When safeFit() later reads viewportY=0, it 'preserves' this
incorrect position at the top of the buffer.

Fix: Track scroll state (viewportY, baseY, wasAtBottom) externally in JS
variables updated via term.onScroll(). These variables are immune to DOM
state changes from display toggling. safeFit() now reads from the tracked
state instead of xterm's buffer.

Also rewrites Terminal.fit-scroll.test.tsx with 7 tests including 3 new
ones specifically for the Issue #560 scenario, and adds the onScroll mock
to all 9 other terminal test files.
@waleedkadous
Copy link
Contributor Author

Architect Review

Clean root cause analysis: xterm's viewportY becomes stale (resets to 0) when container is display:none during tab switches. Previous fix (#423) read directly from the buffer, which returned the stale value.

Fix tracks scroll state externally via term.onScroll() — immune to DOM display toggling. Core change is ~30 lines in Terminal.tsx, well-tested with 4 new regression scenarios + 4 previously-flaky tests un-skipped.

175/175 tests pass. Approved.


Architect review

@waleedkadous waleedkadous merged commit 348326f into main Feb 25, 2026
6 checks passed
@waleedkadous waleedkadous deleted the builder/bugfix-560-terminal-still-scrolls-to-top- branch February 25, 2026 12:36
waleedkadous added a commit that referenced this pull request Feb 25, 2026
PR #561 changed safeFit()'s early-return condition from `if (!baseY)` to
`if (!baseY && !scrollState.baseY)`, adding a dependency on the externally-
tracked scrollState.baseY. This was unnecessary because baseY (from
term.buffer.active.baseY) reflects actual buffer scrollback and is NOT
affected by display:none toggling — unlike viewportY which the external
tracking was designed to protect.

The stale scrollState.baseY could cause safeFit() to take the scroll-
preserving path when the buffer had no scrollback (e.g., after a clear),
leading to incorrect scroll restoration with stale position values.

Fix: Revert to `if (!baseY)` while keeping scrollState.wasAtBottom and
scrollState.viewportY for position values (the core Issue #560 fix).

Adds regression test for the stale scrollState.baseY scenario.

Fixes #563
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Terminal still scrolls to top of buffer (regression from #423)

1 participant