From 275ce7911f0b901cb21ef65ce67791acc542fcae Mon Sep 17 00:00:00 2001 From: Samuel Newman Date: Tue, 9 Jun 2026 16:25:22 +0300 Subject: [PATCH] fix: guard popper against null reference rect in composer autocomplete The mention autocomplete passes @tiptap/suggestion's `clientRect` straight to tippy/popper as `getReferenceClientRect`. That function can return null once the editor tears down, and popper re-invokes it on its debounced `instance.update` - dereferencing `clientRect.left` on null and throwing (APP-P7: ~188k events). Wrap it to cache the last valid rect and never hand popper a null, falling back to a zeroed DOMRect. Removes the two now-moot @ts-ignore comments. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../composer/text-input/web/Autocomplete.tsx | 24 +++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/src/view/com/composer/text-input/web/Autocomplete.tsx b/src/view/com/composer/text-input/web/Autocomplete.tsx index 8371ad993a..7be7cb23ed 100644 --- a/src/view/com/composer/text-input/web/Autocomplete.tsx +++ b/src/view/com/composer/text-input/web/Autocomplete.tsx @@ -40,6 +40,24 @@ export function createSuggestion({ render: () => { let component: ReactRenderer | undefined let popup: TippyInstance[] | undefined + // Popper holds onto getReferenceClientRect and calls it on a debounced + // update. By the time it fires the suggestion may have torn down, in + // which case props.clientRect() returns null and popper crashes on + // `clientRect.left`. Remember the last valid rect and fall back to it so + // popper always receives a real DOMRect. See APP-P7. + let lastClientRect: DOMRect | undefined + + const getReferenceClientRect = ( + clientRect: SuggestionProps['clientRect'], + ) => { + return () => { + const rect = clientRect?.() + if (rect) { + lastClientRect = rect + } + return lastClientRect ?? new DOMRect() + } + } const hide = () => { popup?.[0]?.destroy() @@ -57,9 +75,8 @@ export function createSuggestion({ return } - // @ts-ignore getReferenceClientRect doesnt like that clientRect can return null -prf popup = tippy('body', { - getReferenceClientRect: props.clientRect, + getReferenceClientRect: getReferenceClientRect(props.clientRect), appendTo: () => document.body, content: component.element, showOnCreate: true, @@ -77,8 +94,7 @@ export function createSuggestion({ } popup?.[0]?.setProps({ - // @ts-ignore getReferenceClientRect doesnt like that clientRect can return null -prf - getReferenceClientRect: props.clientRect, + getReferenceClientRect: getReferenceClientRect(props.clientRect), }) },