From d91b8a879cea9cf7cd486686aa6d3751439ecb4d Mon Sep 17 00:00:00 2001 From: Linus Rath <139418639+rathlinus@users.noreply.github.com> Date: Wed, 4 Nov 2026 09:42:40 +0100 Subject: [PATCH] fix: say Draft saved on Ctrl+S also when autosave already had everything The shortcut help test now counts two Ctrl + K entries (command palette and Insert link), and the Cc/Bcc key test waits for the To field's opening focus first. --- .../__tests__/keyboard-shortcuts-sections.test.tsx | 4 +++- components/email/__tests__/composer-close-guard.test.tsx | 8 ++++++++ components/email/email-composer.tsx | 9 ++++++++- 3 files changed, 19 insertions(+), 2 deletions(-) diff --git a/components/__tests__/keyboard-shortcuts-sections.test.tsx b/components/__tests__/keyboard-shortcuts-sections.test.tsx index 635511557..27f4c9ba4 100644 --- a/components/__tests__/keyboard-shortcuts-sections.test.tsx +++ b/components/__tests__/keyboard-shortcuts-sections.test.tsx @@ -37,7 +37,9 @@ describe('keyboard shortcuts help per app', () => { nav.pathname = '/'; render( {}} />); expect(screen.getAllByText('or').length).toBeGreaterThan(0); - expect(screen.getAllByText('Ctrl + K').length).toBe(1); + // The command palette, and Insert link in the composer. + expect(screen.getAllByText('Ctrl + K').length).toBe(2); + expect(screen.queryByText('⌘ + K')).toBeNull(); }); }); diff --git a/components/email/__tests__/composer-close-guard.test.tsx b/components/email/__tests__/composer-close-guard.test.tsx index b1524987e..d6f877d86 100644 --- a/components/email/__tests__/composer-close-guard.test.tsx +++ b/components/email/__tests__/composer-close-guard.test.tsx @@ -418,10 +418,18 @@ describe('composer close guard with a follow-up', () => { const subject = screen.getByDisplayValue('Half-written more'); expect(fireEvent.keyDown(subject, { key: 's', ctrlKey: true })).toBe(false); await waitFor(() => expect(createDraft).toHaveBeenCalledTimes(1)); + + // Nothing new to save: it still says so. + await waitFor(() => expect(screen.getByTestId('composer-save-status')).toHaveAttribute('data-status', 'idle'), { timeout: 4000 }); + fireEvent.keyDown(subject, { key: 's', ctrlKey: true }); + await waitFor(() => expect(screen.getByTestId('composer-save-status')).toHaveAttribute('data-status', 'saved')); + expect(createDraft).toHaveBeenCalledTimes(1); }); it('shows Cc on Ctrl+Shift+C and Bcc on Ctrl+Shift+B, and puts the cursor there', async () => { renderWithCloseRef(); + // A new message puts the cursor in To shortly after it opens; let that pass. + await act(async () => { await new Promise((r) => setTimeout(r, 250)); }); const subject = screen.getByDisplayValue('Half-written more'); expect(fireEvent.keyDown(subject, { key: 'C', ctrlKey: true, shiftKey: true })).toBe(false); await waitFor(() => expect(document.activeElement).toBe(screen.getByRole('combobox', { name: 'cc_label' }))); diff --git a/components/email/email-composer.tsx b/components/email/email-composer.tsx index c32143122..fc292094a 100644 --- a/components/email/email-composer.tsx +++ b/components/email/email-composer.tsx @@ -4846,7 +4846,14 @@ export function EmailComposer({ const key = e.key.toLowerCase(); if (hasPrimaryModifier && !e.altKey && !e.shiftKey && key === 's') { e.preventDefault(); - if (!isSavingAndClosing) void saveDraft({ announce: true }); + // Says "Draft saved" also when autosave already had everything. + if (!isSavingAndClosing) { + void saveDraft({ announce: true }).then((id) => { + if (!id || saveFailedRef.current) return; + setSaveStatus('saved'); + setTimeout(() => setSaveStatus((status) => (status === 'saved' ? 'idle' : status)), 2000); + }); + } return; } if (hasPrimaryModifier && !e.altKey && e.shiftKey && (key === 'c' || key === 'b')) {