History

Defer chat auto-scroll to animation frame

- Queue tail-to-bottom scrolling with `requestAnimationFrame` to avoid redundant scroll calls - Preserve user scroll position by checking auto-scroll intent before running - Cancel any pending frame on unmount to prevent stale callbacks

Julius Marminge committed 8 months ago

Commit 01accc01 parent 0d9c2681 file · +19 −2Browse files

Unified · Split

apps/web/src/components/ChatView.tsx+19−2
@@ -402,6 +402,7 @@ export default function ChatView({ threadId }: ChatViewProps) { · 401 unmodified lines
402 >(() => readLastInvokedScriptByProjectFromStorage());402 >(() => readLastInvokedScriptByProjectFromStorage());
403 const messagesScrollRef = useRef<HTMLDivElement>(null);403 const messagesScrollRef = useRef<HTMLDivElement>(null);
404 const shouldAutoScrollRef = useRef(true);404 const shouldAutoScrollRef = useRef(true);
405 const pendingAutoScrollFrameRef = useRef<number | null>(null);
405 const textareaRef = useRef<HTMLTextAreaElement>(null);406 const textareaRef = useRef<HTMLTextAreaElement>(null);
406 const composerCommandInputRef = useRef<HTMLInputElement>(null);407 const composerCommandInputRef = useRef<HTMLInputElement>(null);
407 const composerImagesRef = useRef<ComposerImageAttachment[]>([]);408 const composerImagesRef = useRef<ComposerImageAttachment[]>([]);
@@ -1102,6 +1103,14 @@ export default function ChatView({ threadId }: ChatViewProps) { · 694 unmodified lines
1102 scrollContainer.scrollTo({ top: scrollContainer.scrollHeight, behavior });1103 scrollContainer.scrollTo({ top: scrollContainer.scrollHeight, behavior });
1103 shouldAutoScrollRef.current = true;1104 shouldAutoScrollRef.current = true;
1104 }, []);1105 }, []);
1106 const requestAutoTailToBottom = useCallback(() => {
1107 if (pendingAutoScrollFrameRef.current !== null) return;
1108 pendingAutoScrollFrameRef.current = window.requestAnimationFrame(() => {
1109 pendingAutoScrollFrameRef.current = null;
1110 if (!shouldAutoScrollRef.current) return;
1111 scrollMessagesToBottom();
1112 });
1113 }, [scrollMessagesToBottom]);
1105 const onMessagesScroll = useCallback(() => {1114 const onMessagesScroll = useCallback(() => {
1106 const scrollContainer = messagesScrollRef.current;1115 const scrollContainer = messagesScrollRef.current;
1107 if (!scrollContainer) return;1116 if (!scrollContainer) return;
@@ -1113,8 +1122,16 @@ export default function ChatView({ threadId }: ChatViewProps) { · 5 unmodified lines
1113 }, [activeThread?.id, scrollMessagesToBottom]);1122 }, [activeThread?.id, scrollMessagesToBottom]);
1114 useLayoutEffect(() => {1123 useLayoutEffect(() => {
1115 if (!shouldAutoScrollRef.current) return;1124 if (!shouldAutoScrollRef.current) return;
1116 scrollMessagesToBottom();1125 requestAutoTailToBottom();
1117 }, [activeThread?.messages, workLogCount, scrollMessagesToBottom]);1126 }, [activeThread?.messages, workLogCount, requestAutoTailToBottom]);
1127 useEffect(
1128 () => () => {
1129 if (pendingAutoScrollFrameRef.current === null) return;
1130 window.cancelAnimationFrame(pendingAutoScrollFrameRef.current);
1131 pendingAutoScrollFrameRef.current = null;
1132 },
1133 [],
1134 );
1118 1135
1119 useEffect(() => {1136 useEffect(() => {
1120 setExpandedWorkGroups({});1137 setExpandedWorkGroups({});