From 420edbabdc12ffaaf0c7df77b7f0ca749d2758e9 Mon Sep 17 00:00:00 2001 From: Ammar Date: Wed, 25 Feb 2026 14:09:33 -0600 Subject: [PATCH 1/7] =?UTF-8?q?=F0=9F=A4=96=20fix:=20reduce=20immersive=20?= =?UTF-8?q?review=20note=20input=20re-renders?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Switch the immersive review composer textarea to an uncontrolled input so each keypress no longer triggers React state updates across the diff surface. Also move autosize work behind requestAnimationFrame scheduling to avoid synchronous per-key layout churn while keeping prefill + submit behavior unchanged. --- _Generated with `mux` • Model: `openai:gpt-5.3-codex` • Thinking: `xhigh` • Cost: `$0.00`_ --- .../components/shared/DiffRenderer.tsx | 59 ++++++++++++++----- 1 file changed, 44 insertions(+), 15 deletions(-) diff --git a/src/browser/components/shared/DiffRenderer.tsx b/src/browser/components/shared/DiffRenderer.tsx index 9ff73a83d7..4d8789ac1c 100644 --- a/src/browser/components/shared/DiffRenderer.tsx +++ b/src/browser/components/shared/DiffRenderer.tsx @@ -706,29 +706,58 @@ const ReviewNoteInput: React.FC = React.memo( initialNoteText, }) => { const { showOld, showNew } = getLineNumberModeFlags(lineNumberMode); - const [noteText, setNoteText] = React.useState(initialNoteText ?? ""); const textareaRef = React.useRef(null); + const resizeFrameRef = React.useRef(null); - // Auto-focus on mount - React.useEffect(() => { - textareaRef.current?.focus(); + const resizeTextarea = React.useCallback(() => { + const textarea = textareaRef.current; + if (!textarea) { + return; + } + + textarea.style.height = "auto"; + textarea.style.height = `${textarea.scrollHeight}px`; }, []); - React.useEffect(() => { - setNoteText(initialNoteText ?? ""); - }, [initialNoteText]); + const scheduleTextareaResize = React.useCallback(() => { + if (resizeFrameRef.current !== null) { + cancelAnimationFrame(resizeFrameRef.current); + } - // Auto-expand textarea as user types + resizeFrameRef.current = window.requestAnimationFrame(() => { + resizeFrameRef.current = null; + resizeTextarea(); + }); + }, [resizeTextarea]); + + // Keep the composer uncontrolled so typing does not trigger per-key React re-renders + // through immersive diff overlays. Parent-initiated prefill changes are synced here. React.useEffect(() => { const textarea = textareaRef.current; - if (!textarea) return; + if (!textarea) { + return; + } - textarea.style.height = "auto"; - textarea.style.height = `${textarea.scrollHeight}px`; - }, [noteText]); + textarea.value = initialNoteText ?? ""; + scheduleTextareaResize(); + }, [initialNoteText, scheduleTextareaResize]); + + // Auto-focus on mount. + React.useEffect(() => { + textareaRef.current?.focus(); + scheduleTextareaResize(); + }, [scheduleTextareaResize]); + + React.useEffect(() => { + return () => { + if (resizeFrameRef.current !== null) { + cancelAnimationFrame(resizeFrameRef.current); + } + }; + }, []); const handleSubmit = () => { - const text = textareaRef.current?.value ?? noteText; + const text = textareaRef.current?.value ?? ""; if (!text.trim()) return; const [start, end] = [selection.startIndex, selection.endIndex].sort((a, b) => a - b); @@ -849,8 +878,8 @@ const ReviewNoteInput: React.FC = React.memo( minHeight: "calc(12px * 1.5 * 2 + 12px)", }} placeholder="Add a review note… (Enter to submit, Shift+Enter for newline, Esc to cancel)" - value={noteText} - onChange={(e) => setNoteText(e.target.value)} + defaultValue={initialNoteText ?? ""} + onInput={scheduleTextareaResize} onClick={(e) => e.stopPropagation()} onKeyDown={(e) => { stopKeyboardPropagation(e); From 76fa8c56e2b6102ae80f3354f903b005ccab1d99 Mon Sep 17 00:00:00 2001 From: Ammar Date: Wed, 25 Feb 2026 19:58:58 -0600 Subject: [PATCH 2/7] =?UTF-8?q?=F0=9F=A4=96=20perf:=20smooth=20immersive?= =?UTF-8?q?=20line-range=20selection=20updates?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reduce line-range selection lag in immersive review by coalescing drag selection updates to one per animation frame and avoiding redundant state writes when selection indices are unchanged. This keeps hunk/cursor sync behavior intact while cutting expensive parent/child re-render churn during rapid mouse drag selection. --- _Generated with `mux` • Model: `openai:gpt-5.3-codex` • Thinking: `xhigh` • Cost: `$0.00`_ --- .../CodeReview/ImmersiveReviewView.tsx | 14 +++- .../components/shared/DiffRenderer.tsx | 83 +++++++++++++++++-- 2 files changed, 88 insertions(+), 9 deletions(-) diff --git a/src/browser/components/RightSidebar/CodeReview/ImmersiveReviewView.tsx b/src/browser/components/RightSidebar/CodeReview/ImmersiveReviewView.tsx index a3ae7fd026..3ab956201d 100644 --- a/src/browser/components/RightSidebar/CodeReview/ImmersiveReviewView.tsx +++ b/src/browser/components/RightSidebar/CodeReview/ImmersiveReviewView.tsx @@ -1111,12 +1111,20 @@ export const ImmersiveReviewView: React.FC = (props) = const anchorIndex = shiftKey ? (selectedLineRangeRef.current?.startIndex ?? activeLineIndexRef.current ?? lineIndex) : lineIndex; - setActiveLineIndex(lineIndex); + setActiveLineIndex((previousLineIndex) => + previousLineIndex === lineIndex ? previousLineIndex : lineIndex + ); if (shiftKey) { - setSelectedLineRange({ startIndex: anchorIndex, endIndex: lineIndex }); + setSelectedLineRange((previousRange) => { + if (previousRange?.startIndex === anchorIndex && previousRange?.endIndex === lineIndex) { + return previousRange; + } + + return { startIndex: anchorIndex, endIndex: lineIndex }; + }); } else { - setSelectedLineRange(null); + setSelectedLineRange((previousRange) => (previousRange === null ? previousRange : null)); } if (isTouchExperience && !shiftKey && resolvedHunk) { diff --git a/src/browser/components/shared/DiffRenderer.tsx b/src/browser/components/shared/DiffRenderer.tsx index 4d8789ac1c..fafba7bf66 100644 --- a/src/browser/components/shared/DiffRenderer.tsx +++ b/src/browser/components/shared/DiffRenderer.tsx @@ -1013,12 +1013,60 @@ export const SelectableDiffRenderer = React.memo( onComposerCancel, }) => { const dragAnchorRef = React.useRef(null); + const dragUpdateFrameRef = React.useRef(null); + const pendingDragLineIndexRef = React.useRef(null); const [isDragging, setIsDragging] = React.useState(false); + const [selection, setSelection] = React.useState(null); + const [selectionInitialNoteText, setSelectionInitialNoteText] = React.useState(""); + + const flushPendingDragSelection = React.useCallback(() => { + const anchorIndex = dragAnchorRef.current; + const pendingLineIndex = pendingDragLineIndexRef.current; + if (anchorIndex === null || pendingLineIndex === null) { + return; + } + + pendingDragLineIndexRef.current = null; + onLineIndexSelect?.(pendingLineIndex, true); + setSelection((previousSelection) => { + if ( + previousSelection?.startIndex === anchorIndex && + previousSelection?.endIndex === pendingLineIndex + ) { + return previousSelection; + } + + return { startIndex: anchorIndex, endIndex: pendingLineIndex }; + }); + }, [onLineIndexSelect]); + + const scheduleDragSelectionUpdate = React.useCallback( + (lineIndex: number) => { + pendingDragLineIndexRef.current = lineIndex; + + if (dragUpdateFrameRef.current !== null) { + return; + } + + dragUpdateFrameRef.current = window.requestAnimationFrame(() => { + dragUpdateFrameRef.current = null; + flushPendingDragSelection(); + }); + }, + [flushPendingDragSelection] + ); React.useEffect(() => { const stopDragging = () => { + if (dragUpdateFrameRef.current !== null) { + cancelAnimationFrame(dragUpdateFrameRef.current); + dragUpdateFrameRef.current = null; + } + + flushPendingDragSelection(); setIsDragging(false); dragAnchorRef.current = null; + pendingDragLineIndexRef.current = null; }; window.addEventListener("mouseup", stopDragging); @@ -1028,10 +1076,17 @@ export const SelectableDiffRenderer = React.memo( window.removeEventListener("mouseup", stopDragging); window.removeEventListener("blur", stopDragging); }; + }, [flushPendingDragSelection]); + + React.useEffect(() => { + return () => { + if (dragUpdateFrameRef.current !== null) { + cancelAnimationFrame(dragUpdateFrameRef.current); + } + }; }, []); + const { theme } = useTheme(); - const [selection, setSelection] = React.useState(null); - const [selectionInitialNoteText, setSelectionInitialNoteText] = React.useState(""); const lastExternalSelectionRequestIdRef = React.useRef(null); const dismissedExternalSelectionRequestIdRef = React.useRef(null); @@ -1236,12 +1291,27 @@ export const SelectableDiffRenderer = React.memo( onLineClick?.(); onLineIndexSelect?.(lineIndex, shiftKey); + if (dragUpdateFrameRef.current !== null) { + cancelAnimationFrame(dragUpdateFrameRef.current); + dragUpdateFrameRef.current = null; + } + pendingDragLineIndexRef.current = null; + const anchor = shiftKey && renderSelectionStartIndex !== null ? renderSelectionStartIndex : lineIndex; dragAnchorRef.current = anchor; setIsDragging(true); setSelectionInitialNoteText(""); - setSelection({ startIndex: anchor, endIndex: lineIndex }); + setSelection((previousSelection) => { + if ( + previousSelection?.startIndex === anchor && + previousSelection?.endIndex === lineIndex + ) { + return previousSelection; + } + + return { startIndex: anchor, endIndex: lineIndex }; + }); }, [onLineClick, onLineIndexSelect, onReviewNote, renderSelectionStartIndex] ); @@ -1252,10 +1322,11 @@ export const SelectableDiffRenderer = React.memo( return; } - onLineIndexSelect?.(lineIndex, true); - setSelection({ startIndex: dragAnchorRef.current, endIndex: lineIndex }); + // Dragging can emit dozens of mouseenter events per second; coalesce updates + // to one per animation frame so immersive line-range selection stays responsive. + scheduleDragSelectionUpdate(lineIndex); }, - [isDragging, onLineIndexSelect] + [isDragging, scheduleDragSelectionUpdate] ); const handleCommentButtonClick = (lineIndex: number, shiftKey: boolean) => { From e1542848e7ecce1c85bcf7be04f99049a0bc1362 Mon Sep 17 00:00:00 2001 From: Ammar Date: Thu, 26 Feb 2026 12:41:49 -0600 Subject: [PATCH 3/7] perf: cut immersive review typing cost for large diffs --- .../components/shared/DiffRenderer.tsx | 76 ++++++++++--------- ...SelectableDiffRenderer.dragSelect.test.tsx | 13 +++- 2 files changed, 49 insertions(+), 40 deletions(-) diff --git a/src/browser/components/shared/DiffRenderer.tsx b/src/browser/components/shared/DiffRenderer.tsx index fafba7bf66..2431ca914c 100644 --- a/src/browser/components/shared/DiffRenderer.tsx +++ b/src/browser/components/shared/DiffRenderer.tsx @@ -122,6 +122,14 @@ const getIndicatorChar = (type: DiffLineType): string => { const REVIEW_RANGE_TINT = "hsl(from var(--color-review-accent) h s l / 0.08)"; +const REVIEW_NOTE_MIN_ROWS = 2; +const REVIEW_NOTE_MAX_ROWS = 8; + +const getTextareaRowsFromNoteText = (text: string): number => { + const explicitLineCount = text.split("\n").length; + return Math.min(REVIEW_NOTE_MAX_ROWS, Math.max(REVIEW_NOTE_MIN_ROWS, explicitLineCount)); +}; + const applyReviewRangeOverlay = (base: string, isActive: boolean): string => { if (!isActive) return base; return `linear-gradient(${REVIEW_RANGE_TINT}, ${REVIEW_RANGE_TINT}), ${base}`; @@ -707,29 +715,17 @@ const ReviewNoteInput: React.FC = React.memo( }) => { const { showOld, showNew } = getLineNumberModeFlags(lineNumberMode); const textareaRef = React.useRef(null); - const resizeFrameRef = React.useRef(null); - const resizeTextarea = React.useCallback(() => { - const textarea = textareaRef.current; - if (!textarea) { + // Avoid scrollHeight reads during typing: in large immersive diffs those force full-grid + // layout on each keypress. Row count based on explicit newlines keeps input latency stable. + const syncTextareaRows = React.useCallback((textarea: HTMLTextAreaElement) => { + const nextRows = getTextareaRowsFromNoteText(textarea.value); + if (textarea.rows === nextRows) { return; } - - textarea.style.height = "auto"; - textarea.style.height = `${textarea.scrollHeight}px`; + textarea.rows = nextRows; }, []); - const scheduleTextareaResize = React.useCallback(() => { - if (resizeFrameRef.current !== null) { - cancelAnimationFrame(resizeFrameRef.current); - } - - resizeFrameRef.current = window.requestAnimationFrame(() => { - resizeFrameRef.current = null; - resizeTextarea(); - }); - }, [resizeTextarea]); - // Keep the composer uncontrolled so typing does not trigger per-key React re-renders // through immersive diff overlays. Parent-initiated prefill changes are synced here. React.useEffect(() => { @@ -739,22 +735,19 @@ const ReviewNoteInput: React.FC = React.memo( } textarea.value = initialNoteText ?? ""; - scheduleTextareaResize(); - }, [initialNoteText, scheduleTextareaResize]); + syncTextareaRows(textarea); + }, [initialNoteText, syncTextareaRows]); // Auto-focus on mount. React.useEffect(() => { - textareaRef.current?.focus(); - scheduleTextareaResize(); - }, [scheduleTextareaResize]); + const textarea = textareaRef.current; + if (!textarea) { + return; + } - React.useEffect(() => { - return () => { - if (resizeFrameRef.current !== null) { - cancelAnimationFrame(resizeFrameRef.current); - } - }; - }, []); + textarea.focus(); + syncTextareaRows(textarea); + }, [syncTextareaRows]); const handleSubmit = () => { const text = textareaRef.current?.value ?? ""; @@ -873,13 +866,13 @@ const ReviewNoteInput: React.FC = React.memo( />