Skip to content

Chat sometimes doesn't scroll all the way to bottom on new message #594

@brendandebeasi

Description

@brendandebeasi

Summary

The Cortex chat panel sometimes fails to scroll all the way to the bottom when a new message arrives, leaving the latest content partially or fully hidden below the fold. Manual scroll is required to see it.

Root cause

interface/src/components/CortexChatPanel.tsx:402-404:

useEffect(() => {
    messagesEndRef.current?.scrollIntoView({behavior: "smooth"});
}, [messages.length, isStreaming, toolActivity.length]);

The dependency array misses several height-changing events:

  1. Tool result expansion is invisible to the effect. In interface/src/hooks/useCortexChat.ts:122-133, the tool_completed SSE event updates an existing toolActivity[i].status from "running""done" and attaches a result_preview. The array length is unchanged, so the effect doesn't re-fire — but the rendered tool card grows in height. If the result preview is tall, it pushes the assistant message below the fold.

  2. ThinkingIndicator toggle doesn't fire it either. At CortexChatPanel.tsx:505, the indicator is shown based on !toolActivity.some(t => t.status === "running"). That's derived state, not a length change, so its appear/disappear doesn't re-scroll.

  3. Async Markdown layout shifts. <Markdown> (CortexChatPanel.tsx:493) renders synchronously, but code-block syntax highlighting, font loading, and embedded image loads reflow heights after the scroll has already finished. Nothing re-scrolls.

  4. behavior: "smooth" races layout. The animation locks onto the target's pixel position when it starts. Between the message arriving (messages.length++), isStreaming → false removing the streaming UI, and toolActivity clearing in the finally block (useCortexChat.ts:167-168), several layout shifts can happen during the in-flight smooth scroll, so it lands short.

Note: messages are not streamed token-by-token — the assistant content arrives whole on the done SSE event (useCortexChat.ts:142-151). So the bug is layout-shift-driven, not streaming-text-driven.

Steps to reproduce (intermittent)

Most reliable when:

  • An assistant turn includes one or more tool calls whose result_preview is tall.
  • Or the assistant response contains code blocks / images that reflow after first paint.

Expected

After any new message or tool result, the chat scrolls fully so the bottom of the latest content is visible.

Actual

Scroll stops short of the bottom; user must manually scroll down.

Suggested fix

Either of:

  • Replace the effect with a ResizeObserver on the messages container that keeps it pinned to bottom whenever scrollHeight grows and the user was already near the bottom (preserve scroll-up intent).
  • Or include tool-status in deps (e.g. toolActivity.map(t => t.status).join(",")), switch to behavior: "auto", and follow up with a requestAnimationFrame (or short setTimeout) re-scroll after layout settles — to catch async markdown reflow.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions