Skip to content

Chat: keep view pinned to bottom while a re-opened thread settles#36

Merged
sysread merged 1 commit into
mainfrom
claude/auto-scroll-last-message-aCHrO
May 12, 2026
Merged

Chat: keep view pinned to bottom while a re-opened thread settles#36
sysread merged 1 commit into
mainfrom
claude/auto-scroll-last-message-aCHrO

Conversation

@sysread
Copy link
Copy Markdown
Owner

@sysread sysread commented May 12, 2026

SYNOPSIS

Opening an existing conversation now lands the view on the last message even after async content finishes painting.

PURPOSE

Currently loadMessages calls scrollToBottom once after a single tick(), so the view lands on the bottom of currently-rendered content. Image attachments, KaTeX, highlight.js, and the inline cohort/intuition panels paint asynchronously after that tick - each one grows scrollHeight while scrollTop stays put, and the latest message drifts up off the bottom of the viewport. The user arrives at the conversation already scrolled away from where they expected to be.

DESCRIPTION

Before: single-shot scrollToBottom(false) after await tick(). No accommodation for post-commit layout growth. The two existing scroll $effects are both gated on sending, so they don't help during thread-load.

Now: a small rAF watchdog (pinBottomWhileSettling) re-pins to bottom whenever scrollHeight grows during the post-load window. It exits on the first of: 6 stable frames (no growth), 3s hard cap, followBottom flipping false (user scrolled up), or another loadMessages superseding it. Thread-switch also calls cancelPostLoadScroll() explicitly so a slow previous thread can't tug the new one back to its bottom.

That keeps the view glued to the newest message across the window where image decode / markdown highlight / panel paint expand the transcript - so the user arrives where they intended to.

Notes:

  • streaming + send paths untouched - they still go through the sending-gated $effects
  • the watchdog respects followBottom, so an early user scroll-up still wins
  • token + element-identity guards prevent a stale rAF from snapping a different thread's container

Generated by Claude Code

Opening an existing conversation called scrollToBottom once after a
single tick, so the view landed on the bottom of the
*currently-rendered* content. But image attachments, KaTeX,
highlight.js, and the inline cohort/intuition panels all paint
asynchronously after that tick. As each one mounted, scrollHeight
grew while scrollTop stayed put - and the last message drifted up off
the bottom of the viewport. The user arrived at the conversation
already scrolled away from where they expected to be.

Replace the single-shot snap with a brief rAF watchdog that re-pins to
bottom whenever scrollHeight grows during the post-load window. It
exits as soon as scrollHeight has been stable for a handful of frames,
or after a 3s hard cap, or the moment the user scrolls up
(followBottom flips false), or another loadMessages supersedes it.
The thread-switch path also cancels any in-flight watchdog explicitly
so a slow-loading previous thread can't tug the new one back to its
bottom.

No changes to the streaming or send paths - those still go through the
sending-gated effects.
@sysread sysread merged commit e34cd54 into main May 12, 2026
1 check passed
@sysread sysread deleted the claude/auto-scroll-last-message-aCHrO branch May 12, 2026 20:54
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.

2 participants