mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-08-28 05:27:24 +00:00
fix: resolve chat scroll anchoring and task-switch scroll race condit… (#11385)
This commit is contained in:
parent
8d57da8bc8
commit
097f648349
2 changed files with 28 additions and 56 deletions
|
|
@ -163,7 +163,7 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
|
|||
const scrollContainerRef = useRef<HTMLDivElement>(null)
|
||||
const stickyFollowRef = useRef<boolean>(false)
|
||||
const [showScrollToBottom, setShowScrollToBottom] = useState(false)
|
||||
const [isAtBottom, setIsAtBottom] = useState(false)
|
||||
const isAtBottomRef = useRef(false)
|
||||
const lastTtsRef = useRef<string>("")
|
||||
const [wasStreaming, setWasStreaming] = useState<boolean>(false)
|
||||
const [checkpointWarning, setCheckpointWarning] = useState<
|
||||
|
|
@ -520,6 +520,23 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
|
|||
}
|
||||
// Reset user response flag for new task
|
||||
userRespondedRef.current = false
|
||||
|
||||
// Ensure new task starts anchored to the bottom. Virtuoso's
|
||||
// initialTopMostItemIndex fires at mount but the message data may
|
||||
// arrive asynchronously, so we also engage sticky follow and
|
||||
// explicitly scroll after a frame to handle the race.
|
||||
let rafId: number | undefined
|
||||
if (task?.ts) {
|
||||
stickyFollowRef.current = true
|
||||
rafId = requestAnimationFrame(() => {
|
||||
virtuosoRef.current?.scrollTo({ top: Number.MAX_SAFE_INTEGER, behavior: "auto" })
|
||||
})
|
||||
}
|
||||
return () => {
|
||||
if (rafId !== undefined) {
|
||||
cancelAnimationFrame(rafId)
|
||||
}
|
||||
}
|
||||
}, [task?.ts])
|
||||
|
||||
const taskTs = task?.ts
|
||||
|
|
@ -1393,7 +1410,7 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
|
|||
|
||||
const handleRowHeightChange = useCallback(
|
||||
(isTaller: boolean) => {
|
||||
if (isAtBottom) {
|
||||
if (isAtBottomRef.current) {
|
||||
if (isTaller) {
|
||||
scrollToBottomSmooth()
|
||||
} else {
|
||||
|
|
@ -1401,7 +1418,7 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
|
|||
}
|
||||
}
|
||||
},
|
||||
[scrollToBottomSmooth, scrollToBottomAuto, isAtBottom],
|
||||
[scrollToBottomSmooth, scrollToBottomAuto],
|
||||
)
|
||||
|
||||
// Disable sticky follow when user scrolls up inside the chat container
|
||||
|
|
@ -1413,23 +1430,6 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
|
|||
}, [])
|
||||
useEvent("wheel", handleWheel, window, { passive: true })
|
||||
|
||||
// Also disable sticky follow when the chat container is scrolled away from bottom
|
||||
useEffect(() => {
|
||||
const el = scrollContainerRef.current
|
||||
if (!el) return
|
||||
const onScroll = () => {
|
||||
// Consider near-bottom within a small threshold consistent with Virtuoso settings
|
||||
const nearBottom = Math.abs(el.scrollHeight - el.scrollTop - el.clientHeight) < 10
|
||||
if (!nearBottom) {
|
||||
stickyFollowRef.current = false
|
||||
}
|
||||
// Keep UI button state in sync with scroll position
|
||||
setShowScrollToBottom(!nearBottom)
|
||||
}
|
||||
el.addEventListener("scroll", onScroll, { passive: true })
|
||||
return () => el.removeEventListener("scroll", onScroll)
|
||||
}, [])
|
||||
|
||||
// Effect to clear checkpoint warning when messages appear or task changes
|
||||
useEffect(() => {
|
||||
if (isHidden || !task) {
|
||||
|
|
@ -1767,9 +1767,12 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
|
|||
itemContent={itemContent}
|
||||
followOutput={(isAtBottom: boolean) => isAtBottom || stickyFollowRef.current}
|
||||
atBottomStateChange={(isAtBottom: boolean) => {
|
||||
setIsAtBottom(isAtBottom)
|
||||
// Only show the scroll-to-bottom button if not at bottom
|
||||
isAtBottomRef.current = isAtBottom
|
||||
setShowScrollToBottom(!isAtBottom)
|
||||
// Clear sticky follow when user scrolls away from bottom
|
||||
if (!isAtBottom) {
|
||||
stickyFollowRef.current = false
|
||||
}
|
||||
}}
|
||||
atBottomThreshold={10}
|
||||
initialTopMostItemIndex={groupedMessages.length - 1}
|
||||
|
|
@ -1898,7 +1901,7 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
|
|||
onSelectImages={selectImages}
|
||||
shouldDisableImages={shouldDisableImages}
|
||||
onHeightChange={() => {
|
||||
if (isAtBottom) {
|
||||
if (isAtBottomRef.current) {
|
||||
scrollToBottomAuto()
|
||||
}
|
||||
}}
|
||||
|
|
|
|||
|
|
@ -299,9 +299,6 @@ const CodeBlock = memo(
|
|||
// potentially changes scrollHeight
|
||||
const wasScrolledUpRef = useRef(false)
|
||||
|
||||
// Ref to track if outer container was near bottom
|
||||
const outerContainerNearBottomRef = useRef(false)
|
||||
|
||||
// Effect to listen to scroll events and update the ref
|
||||
useEffect(() => {
|
||||
const preElement = preRef.current
|
||||
|
|
@ -323,28 +320,6 @@ const CodeBlock = memo(
|
|||
}
|
||||
}, []) // Empty dependency array: runs once on mount
|
||||
|
||||
// Effect to track outer container scroll position
|
||||
useEffect(() => {
|
||||
const scrollContainer = document.querySelector('[data-virtuoso-scroller="true"]')
|
||||
if (!scrollContainer) return
|
||||
|
||||
const handleOuterScroll = () => {
|
||||
const isAtBottom =
|
||||
Math.abs(scrollContainer.scrollHeight - scrollContainer.scrollTop - scrollContainer.clientHeight) <
|
||||
SCROLL_SNAP_TOLERANCE
|
||||
outerContainerNearBottomRef.current = isAtBottom
|
||||
}
|
||||
|
||||
scrollContainer.addEventListener("scroll", handleOuterScroll, { passive: true })
|
||||
|
||||
// Initial check
|
||||
handleOuterScroll()
|
||||
|
||||
return () => {
|
||||
scrollContainer.removeEventListener("scroll", handleOuterScroll)
|
||||
}
|
||||
}, [])
|
||||
|
||||
// Store whether we should scroll after highlighting completes
|
||||
const shouldScrollAfterHighlightRef = useRef(false)
|
||||
|
||||
|
|
@ -471,14 +446,8 @@ const CodeBlock = memo(
|
|||
wasScrolledUpRef.current = false
|
||||
}
|
||||
|
||||
// Also scroll outer container if it was near bottom
|
||||
if (outerContainerNearBottomRef.current) {
|
||||
const scrollContainer = document.querySelector('[data-virtuoso-scroller="true"]')
|
||||
if (scrollContainer) {
|
||||
scrollContainer.scrollTop = scrollContainer.scrollHeight
|
||||
outerContainerNearBottomRef.current = true
|
||||
}
|
||||
}
|
||||
// Outer container scrolling is handled by Virtuoso's followOutput
|
||||
// and ChatView's handleRowHeightChange — no direct DOM manipulation needed.
|
||||
|
||||
// Reset the flag
|
||||
shouldScrollAfterHighlightRef.current = false
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue