From 952eeb73234572f24e83c2dc0a72f3c26517b2af Mon Sep 17 00:00:00 2001 From: Jason Woltje Date: Fri, 6 Feb 2026 18:32:50 -0600 Subject: [PATCH] fix(CQ-WEB-9): Cache DOM measurement element in LinkAutocomplete Replace per-keystroke DOM element creation/removal with a persistent off-screen mirror element stored in useRef. The mirror and cursor span are lazily created on first use and reused for all subsequent caret position measurements, eliminating layout thrashing. Cleanup on component unmount removes the element from the DOM. Co-Authored-By: Claude Opus 4.6 --- .../components/knowledge/LinkAutocomplete.tsx | 56 ++++++++++++------- 1 file changed, 37 insertions(+), 19 deletions(-) diff --git a/apps/web/src/components/knowledge/LinkAutocomplete.tsx b/apps/web/src/components/knowledge/LinkAutocomplete.tsx index fe78b60..9af5b9b 100644 --- a/apps/web/src/components/knowledge/LinkAutocomplete.tsx +++ b/apps/web/src/components/knowledge/LinkAutocomplete.tsx @@ -53,6 +53,8 @@ export function LinkAutocomplete({ const dropdownRef = useRef(null); const searchTimeoutRef = useRef(null); const abortControllerRef = useRef(null); + const mirrorRef = useRef(null); + const cursorSpanRef = useRef(null); /** * Search for knowledge entries matching the query. @@ -132,15 +134,37 @@ export function LinkAutocomplete({ ); /** - * Calculate dropdown position relative to cursor + * Calculate dropdown position relative to cursor. + * Uses a persistent off-screen mirror element (via refs) to avoid + * creating and removing DOM nodes on every keystroke, which causes + * layout thrashing. */ const calculateDropdownPosition = useCallback( (textarea: HTMLTextAreaElement, cursorIndex: number): { top: number; left: number } => { - // Create a mirror div to measure text position - const mirror = document.createElement("div"); - const styles = window.getComputedStyle(textarea); + // Lazily create the mirror element once, then reuse it + if (!mirrorRef.current) { + const mirror = document.createElement("div"); + mirror.style.position = "absolute"; + mirror.style.visibility = "hidden"; + mirror.style.height = "auto"; + mirror.style.whiteSpace = "pre-wrap"; + mirror.style.pointerEvents = "none"; + document.body.appendChild(mirror); + mirrorRef.current = mirror; - // Copy relevant styles + const span = document.createElement("span"); + span.textContent = "|"; + cursorSpanRef.current = span; + } + + const mirror = mirrorRef.current; + const cursorSpan = cursorSpanRef.current; + if (!cursorSpan) { + return { top: 0, left: 0 }; + } + + // Sync styles from the textarea so measurement is accurate + const styles = window.getComputedStyle(textarea); const stylesToCopy = [ "fontFamily", "fontSize", @@ -161,31 +185,19 @@ export function LinkAutocomplete({ } }); - mirror.style.position = "absolute"; - mirror.style.visibility = "hidden"; mirror.style.width = `${String(textarea.clientWidth)}px`; - mirror.style.height = "auto"; - mirror.style.whiteSpace = "pre-wrap"; - // Get text up to cursor + // Update content: text before cursor + cursor marker span const textBeforeCursor = textarea.value.substring(0, cursorIndex); mirror.textContent = textBeforeCursor; - - // Create a span for the cursor position - const cursorSpan = document.createElement("span"); - cursorSpan.textContent = "|"; mirror.appendChild(cursorSpan); - document.body.appendChild(mirror); - const textareaRect = textarea.getBoundingClientRect(); const cursorSpanRect = cursorSpan.getBoundingClientRect(); const top = cursorSpanRect.top - textareaRect.top + textarea.scrollTop + 20; const left = cursorSpanRect.left - textareaRect.left + textarea.scrollLeft; - document.body.removeChild(mirror); - return { top, left }; }, [] @@ -346,7 +358,8 @@ export function LinkAutocomplete({ }, [textareaRef, handleInput, handleKeyDown]); /** - * Cleanup timeout and abort in-flight requests on unmount + * Cleanup timeout, abort in-flight requests, and remove the + * persistent mirror element on unmount */ useEffect(() => { return (): void => { @@ -356,6 +369,11 @@ export function LinkAutocomplete({ if (abortControllerRef.current) { abortControllerRef.current.abort(); } + if (mirrorRef.current) { + document.body.removeChild(mirrorRef.current); + mirrorRef.current = null; + cursorSpanRef.current = null; + } }; }, []);