Skip to content

Chat rail never auto-scrolls to a new message or a live streaming response #7229

Description

@JSONbored

Context

⚠️ Read this before starting. The fix is a scroll-position side effect driven by React state changes —
not a layout/CSS change. Verify it with a DOM scrollTop/scrollHeight assertion (see Requirements),
not by eyeballing a screenshot.

apps/loopover-miner-ui/src/components/chat/message-list.tsx's MessageList renders messages inside
@loopover/ui-kit's ScrollArea (a Radix ScrollArea.Root/.Viewport wrapper —
packages/loopover-ui-kit/src/components/scroll-area.tsx — which does not itself do any auto-scroll; it
is a pure styled-scrollbar primitive). Neither MessageList nor its caller,
apps/loopover-miner-ui/src/components/chat/conversation.tsx's ChatConversation, holds a ref to the
scrollable viewport or ever calls scrollTo/scrollIntoView on it.

As a result, once the conversation grows past the rail's visible height (a ~380px-wide, viewport-height
panel per chat-rail.tsx — trivially exceeded by a handful of turns), a newly-appended message
(ChatConversation's setMessages((prev) => [...prev, ...]), e.g. after a completed streamed answer, or
a portfolio release/requeue system-result message) and the live StreamingText response (rendered
outside MessageList, directly below it in ChatConversation, as tokens accumulate) both append below
whatever the user was last scrolled to, with nothing bringing them into view. An operator who has scrolled
up to re-read an earlier turn — or who simply hasn't scrolled at all once the list first overflows — never
sees a new answer arrive unless they manually scroll down, for both a completed turn and for the
in-progress streaming text.

This is a distinct gap from #7081 (the aria-live="polite" region on the message <ol>, already
shipped): that makes a new message announced to assistive tech; it does nothing for a sighted user's
scroll position, and does not cover the live-streaming render at all (StreamingText lives outside the
aria-live region by design, per message-list.tsx's own comment).

Requirements

  • MessageList (or its caller) must scroll the chat viewport so the latest content is visible when:
    1. messages gains a new entry (length increases), and
    2. while composing/streaming is active, as StreamingText's accumulated text grows (each chunk,
      or throttled — implementer's choice, but it must not wait until the stream completes).
  • The scroll-to-bottom behavior must be conditional on the user already being at (or near) the bottom
    before the new content arrives — if the operator has manually scrolled up to re-read history, a new
    message must not yank their scroll position back down (a well-established chat-UI pattern; do not
    unconditionally force-scroll on every update).
  • Must target the Radix ScrollArea.Viewport element specifically (the actual scrollable node — not the
    outer ScrollArea.Root, which does not scroll itself). packages/loopover-ui-kit/src/components/scroll-area.tsx's
    ScrollArea does not currently forward a ref to the viewport; if a viewport ref is needed and the
    primitive doesn't expose one, extend ScrollArea (or query via [data-radix-scroll-area-viewport],
    the attribute Radix's own primitive sets) rather than replacing the shared component.
  • No change to StateBoundary's loading/empty/error branches, to the aria-live region from Chat responses have no ARIA live region — new messages are never announced to screen readers #7081, or to
    TypingIndicator.

Deliverables

  • Chat rail auto-scrolls to the latest content on a new committed message
  • Chat rail auto-scrolls (or keeps pinned to bottom) while a streamed answer is actively accumulating
  • Scrolling up manually suppresses the auto-scroll until the user returns to (or near) the bottom
  • Regression test(s) in message-list.test.tsx / chat-conversation.test.tsx asserting the
    viewport's scrollTop advances (or is pinned at scrollHeight - clientHeight) after messages
    grows and after simulated streaming chunks, and that it does not move when the container is
    artificially scrolled away from the bottom first

Test Coverage Requirements

apps/** is outside this repo's Codecov coverage.include scope (confirmed in PR #7082's own body), so
this is not gated by codecov/patch. The actual bar is apps/loopover-miner-ui's own Vitest suite
(npm test in that workspace) passing, plus the new regression tests above (jsdom's scrollTop is
writable and readable, and scrollIntoView/scrollTo can be spied/mocked in jsdom, so this is testable
without a real browser).

Expected Outcome

A multi-turn or actively-streaming conversation in the chat rail keeps the newest content in view without
requiring a manual scroll, unless the operator has deliberately scrolled up to review earlier history — in
which case their position is respected until they return to the bottom themselves.

Links & Resources

apps/loopover-miner-ui/src/components/chat/message-list.tsx,
apps/loopover-miner-ui/src/components/chat/conversation.tsx,
apps/loopover-miner-ui/src/components/streaming-text.tsx,
packages/loopover-ui-kit/src/components/scroll-area.tsx, #7081 (the related but distinct ARIA
live-region fix), #6515 / #6518 (the original message-list / wiring issues).

Metadata

Metadata

Assignees

No one assigned

    Labels

    gittensor:bugGittensor-scored bug fix — scores a 0.05x multiplier.help wantedExtra attention is needed

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions