From f4cb1ead4e0a4ff1b161ec4826031dbe4b44b027 Mon Sep 17 00:00:00 2001 From: Marcello Fitton <106866560+angelplusultra@users.noreply.github.com> Date: Tue, 21 Apr 2026 14:48:44 -0700 Subject: [PATCH] fix: long-prompt bubble flicker & See More collapse on streaming/scroll (#5473) fix ui flickering and truncatable prompt expansion bug Co-authored-by: shatfield4 Co-authored-by: Timothy Carambat --- .../ChatHistory/HistoricalMessage/index.jsx | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/frontend/src/components/WorkspaceChat/ChatContainer/ChatHistory/HistoricalMessage/index.jsx b/frontend/src/components/WorkspaceChat/ChatContainer/ChatHistory/HistoricalMessage/index.jsx index 53a0d751..9254e338 100644 --- a/frontend/src/components/WorkspaceChat/ChatContainer/ChatHistory/HistoricalMessage/index.jsx +++ b/frontend/src/components/WorkspaceChat/ChatContainer/ChatHistory/HistoricalMessage/index.jsx @@ -1,4 +1,4 @@ -import React, { memo, useEffect, useRef, useState } from "react"; +import React, { memo, useLayoutEffect, useRef, useState } from "react"; import { Info, Warning } from "@phosphor-icons/react"; import Actions from "./Actions"; import renderMarkdown from "@/utils/chat/markdown"; @@ -22,7 +22,7 @@ import HistoricalOutputs from "./HistoricalOutputs"; import { openImageLightbox } from "@/components/ImageLightbox"; const HistoricalMessage = ({ - uuid = v4(), + uuid: uuidProp, message, role, workspace, @@ -38,6 +38,10 @@ const HistoricalMessage = ({ metrics = {}, outputs = [], }) => { + // Freeze uuid on first render. User messages arrive without a uuid and this value + // is used as the wrapper div's `key` — a default param fallback would regenerate + // on every render and remount the subtree, wiping TruncatableContent state. + const [uuid] = useState(() => uuidProp ?? v4()); const { t } = useTranslation(); const { isEditing } = useEditMessage({ chatId, role }); const { isDeleted, completeDelete, onEndAnimation } = useWatchDeleteMessage({ @@ -238,7 +242,9 @@ function TruncatableContent({ children }) { const [isOverflowing, setIsOverflowing] = useState(false); const { t } = useTranslation(); - useEffect(() => { + // useLayoutEffect (not useEffect) so collapse applies before paint — avoids a + // one-frame flash of uncollapsed content on mount. + useLayoutEffect(() => { if (contentRef.current) { setIsOverflowing(contentRef.current.scrollHeight > 250); }