Skip to content

[bug] Chat yanks you to the bottom every 3s during a loop — loadMessages scrolls unconditionally while its three siblings don't #335

Description

@serge-ivo

The report

I'm looking at the loop messages… the chat always scrolls down even when I'm scrolling. It
shouldn't interrupt my scrolling — if I scroll up trying to read, it should stop automatically
scrolling down.

The guard already exists. One call site ignores it.

InstanceDetail.tsx tracks whether the user is pinned to the bottom (:134-136), set on scroll
(:1048, within 40px). Three of the four auto-scrolls honour it:

line what honours atBottomRef?
:413 new message arrived
:429 live dictation growing
:583 voice pill appeared
:373 inside loadMessages unconditional
// Scroll to bottom after initial load
requestAnimationFrame(() => {
    if (chatRef.current) chatRef.current.scrollTop = chatRef.current.scrollHeight;
});

Why it hits hardest on exactly the messages you were reading

The comment says "after initial load", and as a one-shot that would be right. But loadMessages
is also the refresh path, and pollLoop calls it every three seconds while a loop is running
(:495, driven by setInterval(…, 3000) at :680):

the workflow drives and this only reports — so it also refreshes the transcript, since the
agent's turns arrive from the server rather than from calls this component made.

So during an autonomous run the transcript reloads every 3s and yanks you to the bottom every time.
Loop messages are precisely the ones that arrive while you are not the one talking, which is
precisely when you are scrolling back to read them.

There is a second caller at :727 (a general refresh, guarded on not-working/not-thinking) with
the same effect outside loops, just less often.

Fix

Make the scroll in loadMessages conditional on atBottomRef.current, the same as its three
siblings — with an explicit exception for genuine first load, which is what the comment was
describing. Passing an argument (loadMessages({ initial: true })) keeps the intent visible rather
than inferring it from messages.length === 0.

Worth checking the 40px threshold at :1048 while there: with the composer and the voice pill
overlaying the scroll area, "within 40px of the bottom" may resolve differently than it looks, and
a user who is almost at the bottom being snapped down is the same complaint in a milder form.

Verification

  • Scroll up during a running loop: position holds across at least three poll cycles.
  • Scroll back to the bottom: new messages resume auto-scrolling.
  • Opening a conversation still lands at the newest message.
  • Loading older messages (:392-399) still preserves the reading position — that path already
    works and must not regress.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions