From c22c6b1159ccd0e61db9d40fccba596d6869db7b Mon Sep 17 00:00:00 2001 From: Brandon Noad Date: Thu, 9 Jul 2026 16:17:15 -0300 Subject: [PATCH] fix(ui): focus comment textarea reliably via ref callback MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The CommentPopover focus effect scheduled a setTimeout(0) keyed on [mode], but in popover mode the textarea only renders after `position` is measured by a later effect. Whether the 0ms timer fires before or after that commit is engine-dependent task ordering: Chromium commits the re-render first, so focus lands; WebKit fires the timer first, so textareaRef.current is still null and the comment editor opens unfocused (reproduced in a WKWebView host — the glimpseui integration — where users had to tab or click into the field for every annotation). Focus from a ref callback on the textarea instead: it runs exactly when the element mounts, so there is nothing to race. Mode switches (popover/dialog) still refocus because the textarea remounts. The deferred focus and caret-to-end behavior are preserved; isConnected guards the popover closing before the timer fires. --- packages/ui/components/CommentPopover.tsx | 27 +++++++++++++---------- 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/packages/ui/components/CommentPopover.tsx b/packages/ui/components/CommentPopover.tsx index 991e427a3..08072799a 100644 --- a/packages/ui/components/CommentPopover.tsx +++ b/packages/ui/components/CommentPopover.tsx @@ -167,17 +167,20 @@ export const CommentPopover: React.FC = ({ anchorEl?.scrollIntoView({ block: 'center', behavior: 'smooth' }); }, [anchorEl]); - // Focus textarea on mount and mode changes - useEffect(() => { - const id = setTimeout(() => { - const el = textareaRef.current; - if (el) { - el.focus(); - el.selectionStart = el.selectionEnd = el.value.length; - } + // Focus the textarea when it mounts (initial open and popover/dialog switches). + // A ref callback rather than a mount effect: in popover mode the textarea only + // renders after `position` is measured, and WebKit fires 0ms timers ahead of + // that commit, so an effect keyed on mode alone can run before the textarea + // exists and never focus it (e.g. in WKWebView hosts like Glimpse). + const focusOnMountRef = useCallback((el: HTMLTextAreaElement | null) => { + textareaRef.current = el; + if (!el) return; + setTimeout(() => { + if (!el.isConnected) return; + el.focus(); + el.selectionStart = el.selectionEnd = el.value.length; }, 0); - return () => clearTimeout(id); - }, [mode]); + }, []); // Click-outside for popover mode useEffect(() => { @@ -307,7 +310,7 @@ export const CommentPopover: React.FC = ({ {/* Textarea */}