Skip to content

fix(terminal): keep a scrolled-up reader in place while output streams - #779

Open
jakob2103 wants to merge 1 commit into
HarnessMD:mainfrom
jakob2103:pr/terminal-scroll-anchor
Open

jakob2103 wants to merge 1 commit into
HarnessMD:mainfrom
jakob2103:pr/terminal-scroll-anchor

Conversation

@jakob2103

Copy link
Copy Markdown

Fixes #778.

What this fixes

While an agent streams output into its terminal pane, scrolling up to read an earlier response fails in three ways: the viewport is yanked back to the bottom, wheel scrolls are silently dropped, and a full-screen redraw snaps the viewport to the very top (line 0). You cannot hold a scroll position while output keeps arriving. See #778 for the full repro and root-cause analysis.

This affects agents streaming into the normal screen buffer (Codex, OpenCode, pi, custom CLIs, Claude with the classic tui). Alt-screen / mouse-capture agents (Claude tui: fullscreen) scroll themselves and are left untouched.

Changes

  • terminalPool.ts - introduce an entry.followOutput flag that only user scrolling (wheel / keys / scrollbar) and an explicit snap-to-bottom can change. Stop sampling follow from queued-chunk timing, which was the source of the yank-back.
  • terminalPool.ts / PtyTerminalView.tsx - add a CSI J handler that records the reader's distance-from-bottom on 3J (clear-scrollback), and a write callback that restores it as the redraw refills: the same distance while the buffer rebuilds, then the exact prior line once the buffer is back to its old length. This removes the snap-to-top.
  • terminalAutomation.ts - route the wheel through term.scrollLines via attachCustomWheelEventHandler in the normal buffer, so a repaint between events can no longer drop a wheel flick. Mouse-tracking TUIs and the alt screen keep xterm's default handling.
  • global.css - overflow-anchor: none on .xterm-viewport.

Verification

Reproduced all three behaviours in the running dev app (driven over a debugging port, agent streaming in the normal buffer) before the change. After:

  • Pinned at the bottom it follows (fromBottom 0).
  • One notch up lands ~7 lines up and holds.
  • Scrolled up, scrollTop stays constant through continuous output and through repeated full redraws.

npm run typecheck clean. terminal-automation tests 9/9 pass (2 new cases added in test/terminal-automation.test.cjs).

Notes

  • In tui: fullscreen xterm scrolls itself and already holds position; the new wheel handler deliberately steps aside in that mode, so the fix is scoped to normal-buffer agents.
  • No build/release config is touched - this PR is the terminal fix only.

Three causes, all reproduced in the running app with a streaming agent:

- The follow-the-bottom decision was sampled when a pty chunk was queued
  and applied after xterm parsed it, so chunks already in flight when the
  user scrolled up snapped them back to the bottom. Stick-to-bottom is now
  an entry flag changed only by the user's own scrolling (wheel, keys,
  scrollbar) and the views' explicit snap-to-bottom.
- xterm's wheel path only moves the DOM scrollTop and reads it back on the
  next scroll event; a repaint in between restores the old scrollTop and
  drops the event, so quick wheel flicks during a stream were lost. In the
  normal buffer the wheel now goes through term.scrollLines. Mouse-tracking
  TUIs and the alternate screen keep xterm's default handling.
- A full redraw (CSI 2 J, CSI 3 J, home, reprint) while scrolled up left
  the viewport clamped to line 0, the top of the transcript. The reader is
  now put back where they were once the redraw lands.

Also sets overflow-anchor: none on the xterm viewport so browser scroll
anchoring cannot move it.
@github-actions

Copy link
Copy Markdown
Contributor

🚫 This PR is missing its before/after evidence

Every pull request here has to show its work. Screenshots or a short screen recording, before the change and after it.

  • Before — no image or video under that heading
  • After — no image or video under that heading

How to fix it: edit the description, keep the ### Before and ### After headings from the template, and drag an image or video under each. GitHub uploads it inline. This check re-runs the moment you save.

A bug fix with no visible surface still needs it: show the failing behaviour, then the same steps passing. A terminal recording is fine.

Genuinely nothing to show — a CI tweak, a typo, a dependency bump? A maintainer can apply the no-visual-change label. Please don't ask unless it truly has no observable effect.

📖 CONTRIBUTING.md → Evidence is mandatory

This branch has not been deployed

No deployments
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 viewport jumps to top / snaps to bottom when scrolling up during streaming output

1 participant