Fix/transcript anchor on first token - #339
Open
zopeVaibhav wants to merge 2 commits into
Open
Conversation
zopeVaibhav
requested review from
MikeRyanDev,
davidmckayv,
guidovizoso and
tylerslaton
as code owners
September 2, 2026 18:15
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #338.
What this changes
Keeps the transcript where the reader left it when a Bot's first token arrives. Today it jumps to the first message of the conversation on every turn, because
MessageScrollercounts the element children of its content to work out what changed, andThinking,Stoppedand the queued lines sit in that list alongside the messages. A turn's first token swaps one for the other, the count holds, the scroller reads that as the list having been rewritten and re-anchors on the top of the conversation.Those three are moved into a single always-rendered wrapper, placed ahead of the message rows so the scroller's append arithmetic still lines up, and put back visually with
order. The scroller's own spacer is given a later order so it stays below them rather than between them and the last message.Also in here: the send scroll animates instead of jumping. The scroller calls
scrollTowithbehavior: "auto", which defers to the element's CSS, so the viewport is switched toscroll-behavior: smoothfor the 700ms around a send and switched back. Only the send — an answer streaming in re-anchors on every resize, and smoothing that produces a scroll that never catches up with its own text.prefers-reduced-motionopts out.Where it runs
useRefholding the last user message id, per mounted transcript, in the browser.Boundary and audit
Changelog
Unreleased: The transcript stays with the question when an answer starts arriving.Proof
Reproduced and verified in Chromium against an isolated harness running the real
@shadcn/react@0.3.0MessageScrollerwith the transcript's own child structure — eight messages of history, 600px viewport — reading the position of the question just sent.Before, on
main:scrollTopAfter:
scrollTopEvery send lands identically regardless of the reader's scroll position, and holds through the answer.
The
Thinkingline now sits 24px under the question — onegap-6— with the scroller's spacer below it, rather than 529px down with the spacer in between.The send scroll was sampled mid-animation to confirm it eases rather than jumps:
85, 155, 386, 494, 536, 570, 589, 596, 600, 600.bun run typecheckandbunx biome checkare clean.bun testshows no new failures — the suite has pre-existing failures in the scheduler tests that need a database, nine with this change and eleven on stockmain.fix.mp4