diff --git a/apps/web/src/components/ChatView.tsx b/apps/web/src/components/ChatView.tsx index d81e024f3..75adc0502 100644 --- a/apps/web/src/components/ChatView.tsx +++ b/apps/web/src/components/ChatView.tsx @@ -630,23 +630,46 @@ export default function ChatView({ threadId }: ChatViewProps) { pendingUserInputs.length > 0 || (showPlanFollowUpPrompt && activeProposedPlan !== null); const composerFooterHasWideActions = showPlanFollowUpPrompt || activePendingProgress !== null; + const lastSyncedPendingInputRef = useRef<{ + requestId: string | null; + questionId: string | null; + } | null>(null); useEffect(() => { - if (!activePendingProgress) { + const nextCustomAnswer = activePendingProgress?.customAnswer; + if (typeof nextCustomAnswer !== "string") { + lastSyncedPendingInputRef.current = null; return; } - promptRef.current = activePendingProgress.customAnswer; - setComposerCursor(activePendingProgress.customAnswer.length); + const nextRequestId = activePendingUserInput?.requestId ?? null; + const nextQuestionId = activePendingProgress?.activeQuestion?.id ?? null; + const questionChanged = + lastSyncedPendingInputRef.current?.requestId !== nextRequestId || + lastSyncedPendingInputRef.current?.questionId !== nextQuestionId; + const textChangedExternally = promptRef.current !== nextCustomAnswer; + + lastSyncedPendingInputRef.current = { + requestId: nextRequestId, + questionId: nextQuestionId, + }; + + if (!questionChanged && !textChangedExternally) { + return; + } + + promptRef.current = nextCustomAnswer; + setComposerCursor(nextCustomAnswer.length); setComposerTrigger( detectComposerTrigger( - activePendingProgress.customAnswer, - expandCollapsedComposerCursor( - activePendingProgress.customAnswer, - activePendingProgress.customAnswer.length, - ), + nextCustomAnswer, + expandCollapsedComposerCursor(nextCustomAnswer, nextCustomAnswer.length), ), ); setComposerHighlightedItemId(null); - }, [activePendingProgress, activePendingUserInput?.requestId]); + }, [ + activePendingProgress?.customAnswer, + activePendingUserInput?.requestId, + activePendingProgress?.activeQuestion?.id, + ]); useEffect(() => { attachmentPreviewHandoffByMessageIdRef.current = attachmentPreviewHandoffByMessageId; }, [attachmentPreviewHandoffByMessageId]); diff --git a/apps/web/src/components/ComposerPromptEditor.tsx b/apps/web/src/components/ComposerPromptEditor.tsx index 96efc0fbf..f9321de65 100644 --- a/apps/web/src/components/ComposerPromptEditor.tsx +++ b/apps/web/src/components/ComposerPromptEditor.tsx @@ -629,6 +629,7 @@ function ComposerPromptEditorInner({ const [editor] = useLexicalComposerContext(); const onChangeRef = useRef(onChange); const snapshotRef = useRef({ value, cursor: clampCursor(value, cursor) }); + const isApplyingControlledUpdateRef = useRef(false); useEffect(() => { onChangeRef.current = onChange; @@ -645,21 +646,26 @@ function ComposerPromptEditorInner({ return; } - if (previousSnapshot.value !== value) { - editor.update(() => { - $setComposerEditorPrompt(value); - }); - } - snapshotRef.current = { value, cursor: normalizedCursor }; const rootElement = editor.getRootElement(); - if (!rootElement || document.activeElement !== rootElement) { + const isFocused = Boolean(rootElement && document.activeElement === rootElement); + if (previousSnapshot.value === value && !isFocused) { return; } + isApplyingControlledUpdateRef.current = true; editor.update(() => { - $setSelectionAtComposerOffset(normalizedCursor); + const valueChanged = previousSnapshot.value !== value; + if (previousSnapshot.value !== value) { + $setComposerEditorPrompt(value); + } + if (valueChanged || isFocused) { + $setSelectionAtComposerOffset(normalizedCursor); + } + }); + queueMicrotask(() => { + isApplyingControlledUpdateRef.current = false; }); }, [cursor, editor, value]); @@ -705,9 +711,7 @@ function ComposerPromptEditorInner({ focus: () => { focusAt(snapshotRef.current.cursor); }, - focusAt: (nextCursor: number) => { - focusAt(nextCursor); - }, + focusAt, focusAtEnd: () => { focusAt(snapshotRef.current.value.length); }, @@ -728,6 +732,9 @@ function ComposerPromptEditorInner({ if (previousSnapshot.value === nextValue && previousSnapshot.cursor === nextCursor) { return; } + if (isApplyingControlledUpdateRef.current) { + return; + } snapshotRef.current = { value: nextValue, cursor: nextCursor,