Skip to content

feat(web): replace manual auto-scroll with useStickToBottom in chat thread#1031

Merged
brendan-kellam merged 4 commits intomainfrom
brendan-kellam/use-stick-to-bottom-SOU-455
Mar 23, 2026
Merged

feat(web): replace manual auto-scroll with useStickToBottom in chat thread#1031
brendan-kellam merged 4 commits intomainfrom
brendan-kellam/use-stick-to-bottom-SOU-455

Conversation

@brendan-kellam
Copy link
Contributor

@brendan-kellam brendan-kellam commented Mar 23, 2026

Summary

  • Replaces the custom scroll tracking implementation (scrollAreaRef, latestMessagePairRef, isAutoScrollEnabled state, manual scroll event listeners, scrollIntoView calls) with useStickToBottom, which is already used in detailsCard
  • Fixes sticky "Answer" header layout regressions caused by the scroll container structure change
  • Fixes chat box visibility when answer content exceeds page height

Fixes SOU-455

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Improvements
    • Improved auto-scroll behavior in chat threads for more responsive live updates and reliable message tracking. Scroll position is now preserved and restored more accurately, preventing unexpected jumps while you read or follow streaming conversations and making the "scroll to latest" control more consistent.

…hread

Replaces the custom scroll tracking implementation (manual scroll
listeners, scrollIntoView calls, isAutoScrollEnabled state) with the
useStickToBottom library already used in detailsCard. Fixes layout
regressions for sticky answer headers and chat box visibility.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@github-actions

This comment has been minimized.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 23, 2026

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 7b7ae29b-7dfd-4945-9b5f-16646caac0f8

📥 Commits

Reviewing files that changed from the base of the PR and between d16b5d8 and d6ad6b4.

📒 Files selected for processing (1)
  • packages/web/src/features/chat/components/chatThread/chatThread.tsx

Walkthrough

Replaced Radix ScrollArea and manual scroll refs in the chat thread with the useStickToBottom hook; updated scroll state and invocations, restructured message list markup, adjusted bounce button logic, and added a changelog entry documenting improved auto-scroll behavior.

Changes

Cohort / File(s) Summary
Changelog
CHANGELOG.md
Added an [Unreleased] → Changed entry noting improved auto-scroll behavior in the ask chat thread.
Chat thread component
packages/web/src/features/chat/components/chatThread/chatThread.tsx
Removed Radix ScrollArea and manual refs; integrated useStickToBottom (scrollRef, contentRef, isAtBottom, scrollToBottom); replaced isAutoScrollEnabled with hook state; updated send/restore/submit flows to call scrollToBottom(); changed bounce button condition to !isAtBottom && status === "streaming" and handler to scrollToBottom('instant'); persisted/restored scrollTop via scrollRef.current; restructured markup and removed last-message ref prop from list items.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: replacing manual auto-scroll implementation with the useStickToBottom hook in the chat thread component.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch brendan-kellam/use-stick-to-bottom-SOU-455

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/web/src/features/chat/components/chatThread/chatThread.tsx (1)

279-296: ⚠️ Potential issue | 🟠 Major

Skip the initial restore when no scroll offset was captured.

This effect always falls back to scrollOffset ?? 0. On fresh entries that auto-submit on mount (inputMessage / pending-message restore), that timeout runs after scrollToBottom() and snaps already-scrollable threads back to the top because history.state.scrollOffset has not been recorded yet. Only run the restore when a numeric offset exists.

💡 Suggested guard for the initial restore
     useEffect(() => {
         const scrollElement = scrollRef.current;
         if (!scrollElement) {
             return;
         }
+
+        const { scrollOffset } = (history.state ?? {}) as ChatHistoryState;
+        if (typeof scrollOffset !== 'number') {
+            return;
+        }

         // `@hack`: without this setTimeout, the scroll position would not be restored
         // at the correct position (it was slightly too high). The theory is that the
         // content hasn't fully rendered yet, so restoring the scroll position too
         // early results in weirdness. Waiting 10ms seems to fix the issue.
-        setTimeout(() => {
-            const { scrollOffset } = (history.state ?? {}) as ChatHistoryState;
+        const timeout = window.setTimeout(() => {
             scrollElement.scrollTo({
-                top: scrollOffset ?? 0,
+                top: scrollOffset,
                 behavior: 'instant',
             });
         }, 10);
-    }, [scrollRef]);
+
+        return () => window.clearTimeout(timeout);
+    }, [scrollRef]);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/web/src/features/chat/components/chatThread/chatThread.tsx` around
lines 279 - 296, The effect that restores scroll position in the useEffect
(using scrollRef and ChatHistoryState) should skip restoring when no numeric
scrollOffset exists to avoid snapping fresh threads to top; change the logic
inside the setTimeout to read const { scrollOffset } = (history.state ?? {}) as
ChatHistoryState and only call scrollElement.scrollTo(...) when typeof
scrollOffset === 'number' (otherwise do nothing), so initial mounts with
undefined offsets won't override the current scroll (e.g., after
scrollToBottom()).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/web/src/features/chat/components/chatThread/chatThread.tsx`:
- Around line 363-368: The empty-state wrapper using contentRef lost its
full-height behavior after removing ScrollArea.Viewport, so update the wrapper
div rendered when messagePairs.length === 0 (the div with className "flex
items-center justify-center text-center h-full" inside the ChatThread component)
to preserve height by adding min-h-full to its classes (e.g., "min-h-full") so
it stays vertically centered; adjust the className string on that div
accordingly.

---

Outside diff comments:
In `@packages/web/src/features/chat/components/chatThread/chatThread.tsx`:
- Around line 279-296: The effect that restores scroll position in the useEffect
(using scrollRef and ChatHistoryState) should skip restoring when no numeric
scrollOffset exists to avoid snapping fresh threads to top; change the logic
inside the setTimeout to read const { scrollOffset } = (history.state ?? {}) as
ChatHistoryState and only call scrollElement.scrollTo(...) when typeof
scrollOffset === 'number' (otherwise do nothing), so initial mounts with
undefined offsets won't override the current scroll (e.g., after
scrollToBottom()).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: be1fbf22-6a5f-4288-8e45-ccd0bf3b9a74

📥 Commits

Reviewing files that changed from the base of the PR and between 621ce07 and d16b5d8.

📒 Files selected for processing (2)
  • CHANGELOG.md
  • packages/web/src/features/chat/components/chatThread/chatThread.tsx

@brendan-kellam brendan-kellam merged commit 2a3bd2a into main Mar 23, 2026
6 of 7 checks passed
@brendan-kellam brendan-kellam deleted the brendan-kellam/use-stick-to-bottom-SOU-455 branch March 23, 2026 20:37
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.

1 participant