From b812fe62f4a36c7b8cde741248c531bfd9fad7c8 Mon Sep 17 00:00:00 2001 From: Linus Rath <139418639+rathlinus@users.noreply.github.com> Date: Tue, 20 Oct 2026 21:24:03 +0200 Subject: [PATCH 1/2] fix: keep the composer's editor-ready handler stable so a repeated report can't loop --- components/email/email-composer.tsx | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/components/email/email-composer.tsx b/components/email/email-composer.tsx index cc9972586..211c89f6c 100644 --- a/components/email/email-composer.tsx +++ b/components/email/email-composer.tsx @@ -2753,6 +2753,17 @@ export function EmailComposer({ const wantsPluginSelection = wantsPluginDraft; const [pluginSelection, setPluginSelection] = useState<{ text: string; empty: boolean }>(EMPTY_SELECTION); const [pluginEditor, setPluginEditor] = useState(null); + // One callback for the composer's life: the editor reports itself ready + // whenever this changes, and every report now sets state, so a new arrow + // per render would report, render and report again without end. + const handleEditorReady = useCallback((ed: Editor) => { + editorRef.current = ed; + setPluginEditor(ed); + if (!replyFocusedRef.current && (mode === 'reply' || mode === 'replyAll')) { + replyFocusedRef.current = true; + ed.commands.focus('start'); + } + }, [mode]); useEffect(() => { if (!wantsPluginSelection) return; let timer: ReturnType | null = null; @@ -4158,14 +4169,7 @@ export function EmailComposer({ hasError={validationErrors.body} collapseToolbarOnMobile={!showFormattingOnMobile} toolbarPlacement={mvWindow ? (mvShowFormatting ? 'bottom' : 'hidden') : 'top'} - onEditorReady={(ed) => { - editorRef.current = ed; - setPluginEditor(ed); - if (!replyFocusedRef.current && (mode === 'reply' || mode === 'replyAll')) { - replyFocusedRef.current = true; - ed.commands.focus('start'); - } - }} + onEditorReady={handleEditorReady} mentionCandidates={recipientMentionsEnabled ? mentionCandidates : undefined} findMentionContacts={recipientMentionsEnabled && mentionAddsRecipientTo !== 'off' ? findMentionContacts : undefined} mentionAddsTo={mentionAddsRecipientTo === 'cc' ? 'cc' : 'to'} From 24317a7d926a2979675553a20e5d6c2c6874ecd7 Mon Sep 17 00:00:00 2001 From: Linus Rath <139418639+rathlinus@users.noreply.github.com> Date: Tue, 20 Oct 2026 21:24:27 +0200 Subject: [PATCH 2/2] test: drag the composer's plugin panels with pointer events --- .../__tests__/composer-plugin-surfaces.test.tsx | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/components/email/__tests__/composer-plugin-surfaces.test.tsx b/components/email/__tests__/composer-plugin-surfaces.test.tsx index f236cba0b..c9812f7c9 100644 --- a/components/email/__tests__/composer-plugin-surfaces.test.tsx +++ b/components/email/__tests__/composer-plugin-surfaces.test.tsx @@ -518,17 +518,19 @@ describe('composer-sidebar and composer-sidebar-right', () => { offerSlots('p1', ['composer-sidebar', 'composer-sidebar-right']); const { unmount } = render(); + // A mouse drag, as pointer events with the button held (the handle follows pointers). + const drag = (handle: HTMLElement, from: number, to: number) => { + fireEvent.pointerDown(handle, { pointerId: 1, button: 0, buttons: 1, pointerType: 'mouse', clientX: from, clientY: 10 }); + fireEvent.pointerMove(document, { pointerId: 1, buttons: 1, pointerType: 'mouse', clientX: to, clientY: 10 }); + fireEvent.pointerUp(document, { pointerId: 1, button: 0, buttons: 0, pointerType: 'mouse' }); + }; // The right panel's handle is on its left edge: dragging left widens it. const right = screen.getByTestId('composer-sidebar-resize-right'); - fireEvent.mouseDown(right, { clientX: 1000 }); - fireEvent.mouseMove(document, { clientX: 900 }); - fireEvent.mouseUp(document); + drag(right, 1000, 900); expect(panel('right').style.width).toBe('360px'); // The left panel's handle is on its right edge: dragging left narrows it, down to the minimum. const left = screen.getByTestId('composer-sidebar-resize-left'); - fireEvent.mouseDown(left, { clientX: 300 }); - fireEvent.mouseMove(document, { clientX: 100 }); - fireEvent.mouseUp(document); + drag(left, 300, 100); expect(panel('left').style.width).toBe('220px'); fireEvent.keyDown(left, { key: 'ArrowRight' }); expect(panel('left').style.width).toBe('230px');