You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
⚠️ 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 outsideMessageList, 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:
messages gains a new entry (length increases), and
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.
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).
Context
apps/loopover-miner-ui/src/components/chat/message-list.tsx'sMessageListrendersmessagesinside@loopover/ui-kit'sScrollArea(a RadixScrollArea.Root/.Viewportwrapper —packages/loopover-ui-kit/src/components/scroll-area.tsx— which does not itself do any auto-scroll; itis a pure styled-scrollbar primitive). Neither
MessageListnor its caller,apps/loopover-miner-ui/src/components/chat/conversation.tsx'sChatConversation, holds a ref to thescrollable viewport or ever calls
scrollTo/scrollIntoViewon it.As a result, once the conversation grows past the rail's visible height (a
~380px-wide, viewport-heightpanel per
chat-rail.tsx— trivially exceeded by a handful of turns), a newly-appended message(
ChatConversation'ssetMessages((prev) => [...prev, ...]), e.g. after a completed streamed answer, ora portfolio release/requeue system-result message) and the live
StreamingTextresponse (renderedoutside
MessageList, directly below it inChatConversation, as tokens accumulate) both append belowwhatever 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>, alreadyshipped): 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 (
StreamingTextlives outside thearia-liveregion by design, permessage-list.tsx's own comment).Requirements
MessageList(or its caller) must scroll the chat viewport so the latest content is visible when:messagesgains a new entry (length increases), andcomposing/streaming is active, asStreamingText's accumulated text grows (each chunk,or throttled — implementer's choice, but it must not wait until the stream completes).
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).
ScrollArea.Viewportelement specifically (the actual scrollable node — not theouter
ScrollArea.Root, which does not scroll itself).packages/loopover-ui-kit/src/components/scroll-area.tsx'sScrollAreadoes not currently forward a ref to the viewport; if a viewport ref is needed and theprimitive 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.
StateBoundary's loading/empty/error branches, to thearia-liveregion from Chat responses have no ARIA live region — new messages are never announced to screen readers #7081, or toTypingIndicator.Deliverables
message-list.test.tsx/chat-conversation.test.tsxasserting theviewport's
scrollTopadvances (or is pinned atscrollHeight - clientHeight) aftermessagesgrows 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 Codecovcoverage.includescope (confirmed in PR #7082's own body), sothis is not gated by
codecov/patch. The actual bar isapps/loopover-miner-ui's own Vitest suite(
npm testin that workspace) passing, plus the new regression tests above (jsdom'sscrollTopiswritable and readable, and
scrollIntoView/scrollTocan be spied/mocked in jsdom, so this is testablewithout 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 ARIAlive-region fix), #6515 / #6518 (the original message-list / wiring issues).