From b2292418546d46d61f114928db208cc7240117de Mon Sep 17 00:00:00 2001 From: josh <144584931+dancer@users.noreply.github.com> Date: Tue, 16 Sep 2025 11:41:52 +0100 Subject: [PATCH] fix(ui): reasoning display glitches and scroll button persistence (#1206) --- components/message-reasoning.tsx | 11 +++++++++- components/multimodal-input.tsx | 35 ------------------------------ hooks/use-scroll-to-bottom.tsx | 37 +++++++++++++++++++++++++++++++- 3 files changed, 46 insertions(+), 37 deletions(-) diff --git a/components/message-reasoning.tsx b/components/message-reasoning.tsx index 49ae6ca..2030413 100644 --- a/components/message-reasoning.tsx +++ b/components/message-reasoning.tsx @@ -1,5 +1,6 @@ 'use client'; +import { useState, useEffect } from 'react'; import { Reasoning, ReasoningTrigger, @@ -15,10 +16,18 @@ export function MessageReasoning({ isLoading, reasoning, }: MessageReasoningProps) { + const [hasBeenStreaming, setHasBeenStreaming] = useState(isLoading); + + useEffect(() => { + if (isLoading) { + setHasBeenStreaming(true); + } + }, [isLoading]); + return ( diff --git a/components/multimodal-input.tsx b/components/multimodal-input.tsx index de58421..970c328 100644 --- a/components/multimodal-input.tsx +++ b/components/multimodal-input.tsx @@ -38,9 +38,6 @@ import { SelectItem } from '@/components/ui/select'; import * as SelectPrimitive from '@radix-ui/react-select'; import equal from 'fast-deep-equal'; import type { UseChatHelpers } from '@ai-sdk/react'; -import { AnimatePresence, motion } from 'framer-motion'; -import { ArrowDown } from 'lucide-react'; -import { useScrollToBottom } from '@/hooks/use-scroll-to-bottom'; import type { VisibilityType } from './visibility-selector'; import type { Attachment, ChatMessage } from '@/lib/types'; import type { AppUsage } from '@/lib/usage'; @@ -234,40 +231,8 @@ function PureMultimodalInput({ [setAttachments], ); - const { isAtBottom, scrollToBottom } = useScrollToBottom(); - - useEffect(() => { - if (status === 'submitted') { - scrollToBottom(); - } - }, [status, scrollToBottom]); - return (
- - {!isAtBottom && ( - - - - )} - {messages.length === 0 && attachments.length === 0 && diff --git a/hooks/use-scroll-to-bottom.tsx b/hooks/use-scroll-to-bottom.tsx index 8143077..262eae3 100644 --- a/hooks/use-scroll-to-bottom.tsx +++ b/hooks/use-scroll-to-bottom.tsx @@ -17,7 +17,42 @@ export function useScrollToBottom() { // Check if we are within 100px of the bottom (like v0 does) setIsAtBottom(scrollTop + clientHeight >= scrollHeight - 100); - }, []); + }, []); + + useEffect(() => { + if (!containerRef.current) return; + + const container = containerRef.current; + + const resizeObserver = new ResizeObserver(() => { + requestAnimationFrame(() => { + handleScroll(); + }); + }); + + const mutationObserver = new MutationObserver(() => { + requestAnimationFrame(() => { + requestAnimationFrame(() => { + handleScroll(); + }); + }); + }); + + resizeObserver.observe(container); + mutationObserver.observe(container, { + childList: true, + subtree: true, + attributes: true, + attributeFilter: ['style', 'class', 'data-state'] + }); + + handleScroll(); + + return () => { + resizeObserver.disconnect(); + mutationObserver.disconnect(); + }; + }, [handleScroll]); useEffect(() => { const container = containerRef.current;