Skip to content

fix(tui): keep the reader's place while scrolled up in a long session - #53219

Closed
Nowaker wants to merge 1 commit into
anomalyco:devfrom
Nowaker:fix-scroll-follow
Closed

Nowaker wants to merge 1 commit into
anomalyco:devfrom
Nowaker:fix-scroll-follow

Conversation

@Nowaker

@Nowaker Nowaker commented Oct 4, 2026 •

Copy link
Copy Markdown

Note: I'm aware v1 is now the maintenance branch and new features go to v2 only. This fix is here for others who want it on v1. I will submit a v2 version once I've migrated my plugin setup to v2.

Issue for this PR

Closes #41243 (root cause analysis of #29094 / #4196). The earlier fix for it, #41247, was closed by the automated PR cleanup without a review.

Type of change

  • Bug fix
  • New feature
  • Refactor / code improvement
  • Documentation

What does this PR do?

The sync store keeps at most 100 messages per session and drops the oldest one on every new message (context/sync.tsx). Each drop removes rows above the viewport while scrollTop stays fixed, so a reader scrolled up in a long session sees the text slide up with every new assistant message. Once it reaches the bottom, sticky scroll re-engages and follows the stream again. Short sessions never hit the cap. That is why the scrollbox looks correct in isolation: sticky scroll itself is fine, as #41243 found.

keepScrollAnchor() (in util/scroll.ts) listens to the root's layout-changed event. That event fires after the new layout is computed and before any renderable reads it. When the scrollbox is not at the bottom, it takes the first child that was visible under the old layout and scrolls by the distance that child moved, so the frame is drawn with the view in place. At the bottom it does nothing, and following the stream is unchanged.

Compared with #41247, this measures the layout change itself instead of predicting the pruned height from the message list. It needs no id wrapper on assistant messages, and it covers any other height change above the viewport too.

How did you verify your code works?

  • New packages/tui/test/util/scroll.test.tsx renders a sticky-bottom scrollbox and rotates items the way the transcript does. Scrolled up, the first visible line stays put (item 25). Without the fix it drifts to item 28 after three rotations. At the bottom, new items are still followed. bun typecheck passes, and the TUI suite passes apart from DiffViewerFileTree > does not render text markers for highlighted rows, which is flaky under full-suite load and passes alone.
  • Drove a real TUI in tmux (isolated XDG dirs) against a local fake OpenAI-compatible server. The server streams markdown, reasoning and a bash tool call per step, so the session grows past 100 messages. I sampled the first and last visible step markers once per second after scrolling up five wheel notches:

Before (dev), the view slides down the transcript:

13:33:08  Step 96 prose      Step 96 closing
13:33:12  Step 98 findings   step98(
13:33:17  Step 99 findings   step99(
13:33:21  Step 100 findings  step100(
13:33:25  Step 101 findings  step101(

After, the view holds for 40s while the stream keeps going, and End resumes following:

14:45:53  Step 586 closing  Step 587 bullet two
14:46:10  Step 586 closing  Step 587 bullet two
14:46:30  Step 586 closing  Step 587 bullet two
--- End
14:46:36  Step 595 closing  Step 595 closing
14:46:39  Step 596 findings step596(

In sessions under 100 messages, scrolled-up reading already holds on dev for wheel, PageUp and line-up. This change leaves that path alone.

Screenshots / recordings

Text frames above (tmux capture-pane).

Checklist

  • I have tested my changes locally
  • I have not included unrelated changes in this PR

This PR is AI generated, but under direct supervision and on request of @Nowaker.

The sync store keeps at most 100 messages per session and drops the
oldest one whenever a new message arrives (context/sync.tsx). Each drop
removes rows from the top of the transcript scrollbox while scrollTop
stays put, so a reader scrolled up into history sees the text slide
upward on every new assistant message, and once it reaches the bottom
sticky scroll re-engages and follows the stream again. Short sessions
never hit the cap, which is why the bug only shows up in long agentic
runs.

keepScrollAnchor() listens for the root's "layout-changed" event, which
fires after the new layout is computed but before any renderable reads
it. While the scrollbox is not at the bottom it takes the first child
that was visible under the old layout and scrolls by the distance that
child moved, so the view stays put before the frame is drawn. At the
bottom it does nothing and sticky scroll keeps following as before.
@github-actions

github-actions Bot commented Oct 4, 2026

Copy link
Copy Markdown
Contributor

The following comment was made by an LLM, it may be inaccurate:

Nowaker added a commit to Nowaker/opencode that referenced this pull request Oct 4, 2026
…session

New feature and session records for 6ff168d, which re-anchors the
session transcript after every layout so the 100-message prune no longer
slides a scrolled-up view. Upstream PR anomalyco#53219 closes the existing issue
anomalyco#41243. The stream-throttle feature gets a re-verified timeline entry.

AI-Tool: opencode 1.18.33-vt-60-2fa3363c92
AI-Model: anthropic/claude-opus-5-5
AI-Variant: high
AI-Platform: linux
AI-Harness: Vibeterm 3ae7f02
AI-Session-ID: ses_ef81ca107ffet5DwnFLaGWlUap
@Nowaker
Nowaker force-pushed the fix-scroll-follow branch from ecfa7cb to 556b9e0 Compare October 7, 2026 11:13
@thdxr

thdxr commented Oct 10, 2026

Copy link
Copy Markdown
Member

This branch has been upgraded to V2. We're no longer taking PRs for V1. If you think this is still relevant, please port it over and open it for V2.

— from 𝕺𝖕𝖊𝖓𝕮𝖔𝖉𝖊

@thdxr thdxr closed this Oct 10, 2026
Nowaker added a commit to Nowaker/opencode that referenced this pull request Oct 11, 2026
…session

New feature and session records for 6ff168d, which re-anchors the
session transcript after every layout so the 100-message prune no longer
slides a scrolled-up view. Upstream PR anomalyco#53219 closes the existing issue
anomalyco#41243. The stream-throttle feature gets a re-verified timeline entry.

AI-Tool: opencode 1.18.33-vt-60-2fa3363c92
AI-Model: anthropic/claude-opus-5-5
AI-Variant: high
AI-Platform: linux
AI-Harness: Vibeterm 3ae7f02
AI-Session-ID: ses_ef81ca107ffet5DwnFLaGWlUap
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.

Root cause analysis: viewport drifts while reading a backlog during generation (ref #29094)

2 participants