fix(chat): make the transcript follow the answer as one scroll model - #174
Merged
Merged
Conversation
The transcript's auto-scroll had three symptoms that share one cause: it
treated "scroll to bottom" as an anchor scroll and the composer's height as a
constant.
- Follow only while the reader is near the bottom (48px, measured as
`scrollHeight - scrollTop - clientHeight`), so scrolling up to re-read stops
the follow instead of fighting it. The distance is clamped at zero for
sub-pixel layout.
- Drive the follow from the content's height with a `ResizeObserver`, not only
from the messages array: streaming grows one message, and Markdown reflow, a
highlighted code block, a decoded image or an opened source disclosure change
the height with no message event.
- Land on the viewport's true bottom (`scrollTo({ top: scrollHeight })`) rather
than an anchor's `scrollIntoView`, which stopped short of the reserved
composer space and left the last line behind the input.
- Measure the floating composer with a `ResizeObserver` and publish it as
`--composer-reserve`; the transcript's padding, the scroll fade and the
back-to-bottom control all read that one value. The old fixed `pb-32` reserve
could not track a growing scope row or a multi-line question.
- Turn the back-to-bottom control into a circular, icon-only `↓` centred above
the composer. Smooth for a deliberate jump, instant while an answer streams
or under `prefers-reduced-motion`; a programmatic lock keeps the animation
from unfollowing halfway down.
- Remount the transcript per session so every conversation opens at its end.
The scroll arithmetic moves to `chat/stickToBottom.ts` and is covered by
`test/stickToBottom.test.ts`.
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.
What does this PR do?
Reworks the chat transcript's auto-scroll into a single follow model:
follow only while the reader is near the bottom, stop the moment they scroll
up, reserve the composer's real height, and offer one icon-only back-to-bottom
control.
Why?
Three reported symptoms share one cause:
re-read a passage.
composer, because it scrolled to an anchor and the composer's height was a
hardcoded
pb-32that a growing scope row or a multi-line question outpaced.highlighting, images, an opened source disclosure) did not keep the follow
alive.
What changed?
scrollHeight - scrollTop - clientHeightagainst a 48pxthreshold, clamped at zero for sub-pixel layout, instead of
scrollTop + clientHeight === scrollHeight.ResizeObserveron the transcript drives the follow from content height,so streaming and reflow both keep the end in view — only while pinned.
scrollToBottomtargets the viewport's true bottom(
scrollTo({ top: scrollHeight })), not an anchor'sscrollIntoView.ProcessPanelmeasures the composer and publishes--composer-reserve; thetranscript's
padding-bottom, the scroll fade height and the back-to-bottomcontrol's offset all read that value.
↓centred above thecomposer (32×32, tooltip "Back to bottom" / "回到底部"). Smooth for a
deliberate jump; instant while an answer streams or under
prefers-reduced-motion. A programmatic lock keeps the animation fromunfollowing part-way, and wheel/touch/keyboard input cancels it.
end.
chat/stickToBottom.tswithtest/stickToBottom.test.ts; DESIGN.md describes the new model.How was this tested?
npm run typecheck— passesnpm test— 428 pass (incl. 11 newstickToBottomcases: thresholdboundary, fractional pixels, negative clamp)
npm run build— passesnpm run check:design— no violationsnpm run lint— no findings in the changed files (existinganywarningsunchanged)
Screenshots / recordings
Required for meaningful UI/UX changes. Not attached — no running desktop
session was available. The reviewer should confirm in
npm run dev:the
↓position above single-line / multi-line / scope-expanded composers, thesmooth jump, and that the last line stays clear of the input.
Checklist
npm run typecheckpasses.npm run buildpasses.Desktop / build changes