fix: Use assistant-ui scroll primitives to fix auto-scroll and bottom padding #5

Merged
dries merged 3 commits from fix/scroll-to-bottom-auto-scroll into main 2026-05-03 22:24:47 +02:00
Owner

Summary

  • Replace ~120 lines of custom scroll management with the library's built-in ThreadPrimitive.Viewport, ViewportFooter, and ScrollToBottom
  • Fix race condition where auto-scroll was suppressed when new messages arrived while viewing the last message
  • Fix bottom padding issue where the last message could be partially hidden behind the composer

Problem

The conversation view had two scroll issues:

  1. Auto-scroll not working: When viewing the last message and a new one arrived, the UI didn't scroll down automatically — only showed the scroll-to-bottom button
  2. Padding issue: The last message/block could be partially hidden behind the composer overlay

Root cause

AssistantThread rolled its own scroll management instead of using @assistant-ui/react's built-in primitives. The custom code had a race condition: wasAtBottomRef was read lazily inside a requestAnimationFrame callback, but a scroll event between the MutationObserver and the RAF could flip it to false (because scrollHeight grew), suppressing auto-scroll.

The padding issue came from manually tracking bottomInset (composer height) via a separate MutationObserver + ResizeObserver — fragile and could get out of sync.

Changes

File Change
AssistantThread.tsx Replace custom <div> viewport with <ThreadPrimitive.Viewport autoScroll>, wrap composer in <ThreadPrimitive.ViewportFooter>, replace custom scroll button with <ThreadPrimitive.ScrollToBottom>. Remove custom scroll logic.
AssistantThread.css Remove fixed padding-bottom, add .oc-viewport-footer sticky positioning, dynamic scroll button placement via --oc-footer-height CSS variable.
Composer.css Remove position: absolute (now inside sticky ViewportFooter).
PermissionPrompt.css Same — remove absolute positioning.
QuestionPrompt.css Same — remove absolute positioning.

What's preserved

  • Upward pagination (scroll near top triggers onLoadMore)
  • Scroll preservation on prepend (adjusts scrollTop after older messages load)
  • Alt+H/Alt+L keyboard shortcuts for jumping between user messages
  • Link hardening (MutationObserver + requestIdleCallback)
## Summary - Replace ~120 lines of custom scroll management with the library's built-in `ThreadPrimitive.Viewport`, `ViewportFooter`, and `ScrollToBottom` - Fix race condition where auto-scroll was suppressed when new messages arrived while viewing the last message - Fix bottom padding issue where the last message could be partially hidden behind the composer ## Problem The conversation view had two scroll issues: 1. **Auto-scroll not working**: When viewing the last message and a new one arrived, the UI didn't scroll down automatically — only showed the scroll-to-bottom button 2. **Padding issue**: The last message/block could be partially hidden behind the composer overlay ## Root cause `AssistantThread` rolled its own scroll management instead of using `@assistant-ui/react`'s built-in primitives. The custom code had a race condition: `wasAtBottomRef` was read lazily inside a `requestAnimationFrame` callback, but a scroll event between the MutationObserver and the RAF could flip it to `false` (because `scrollHeight` grew), suppressing auto-scroll. The padding issue came from manually tracking `bottomInset` (composer height) via a separate MutationObserver + ResizeObserver — fragile and could get out of sync. ## Changes | File | Change | |------|--------| | `AssistantThread.tsx` | Replace custom `<div>` viewport with `<ThreadPrimitive.Viewport autoScroll>`, wrap composer in `<ThreadPrimitive.ViewportFooter>`, replace custom scroll button with `<ThreadPrimitive.ScrollToBottom>`. Remove custom scroll logic. | | `AssistantThread.css` | Remove fixed `padding-bottom`, add `.oc-viewport-footer` sticky positioning, dynamic scroll button placement via `--oc-footer-height` CSS variable. | | `Composer.css` | Remove `position: absolute` (now inside sticky ViewportFooter). | | `PermissionPrompt.css` | Same — remove absolute positioning. | | `QuestionPrompt.css` | Same — remove absolute positioning. | ## What's preserved - Upward pagination (scroll near top triggers `onLoadMore`) - Scroll preservation on prepend (adjusts `scrollTop` after older messages load) - Alt+H/Alt+L keyboard shortcuts for jumping between user messages - Link hardening (MutationObserver + requestIdleCallback)
fix: Use assistant-ui scroll primitives to fix auto-scroll and bottom padding
Some checks failed
CI / Frontend (pull_request) Successful in 48s
CI / Backend (pull_request) Has been cancelled
CI / Playwright E2E (pull_request) Has been cancelled
CI / Build (pull_request) Has been cancelled
e1ff31530f
Replace ~120 lines of custom scroll management (MutationObserver,
wasAtBottomRef, bottomInset tracking, RAF-coalesced auto-scroll) with
the library's built-in ThreadPrimitive.Viewport, ViewportFooter, and
ScrollToBottom. The custom code had a race condition where wasAtBottom
was read lazily inside a RAF callback — a scroll event between the
mutation and the RAF could flip it to false, suppressing auto-scroll.

The library's useOnResizeContent + useThreadViewportAutoScroll handles
this correctly via a scrollingToBottomBehaviorRef pattern that avoids
the stale-state problem.

Also moves the composer/permission/question overlays from absolute
positioning into a sticky ViewportFooter, so the viewport's scroll
math automatically accounts for their height.
fix: Align composer width with conversation blocks
Some checks failed
CI / Frontend (pull_request) Successful in 47s
CI / Backend (pull_request) Successful in 36s
CI / Playwright E2E (pull_request) Failing after 2m24s
CI / Build (pull_request) Has been cancelled
da2d216baa
fix: Thinking dots use 5 squares, fix initial state and typing lag
Some checks failed
CI / Frontend (pull_request) Successful in 47s
CI / Backend (pull_request) Successful in 35s
CI / Playwright E2E (pull_request) Successful in 2m25s
CI / Build (pull_request) Successful in 1m11s
CI / Frontend (push) Successful in 48s
CI / Backend (push) Successful in 36s
CI / Playwright E2E (push) Failing after 2m27s
Semantic Tag / tag (push) Successful in 3s
CI / Build (push) Successful in 1m11s
12e6748e3f
- Add 5 thinking dots instead of 3 with staggered delays
- Set default element style to the low state (opacity 0.3, scale 0.8)
  so all dots start dim before their animation delay kicks in
- RAF-coalesce the ViewportFooter height observer to avoid layout
  thrash on every keystroke
dries merged commit 12e6748e3f into main 2026-05-03 22:24:47 +02:00
dries deleted branch fix/scroll-to-bottom-auto-scroll 2026-05-03 22:24:47 +02:00
Sign in to join this conversation.
No description provided.