diff --git a/components/email/email-composer.tsx b/components/email/email-composer.tsx index ccdb1bc3..9d612259 100644 --- a/components/email/email-composer.tsx +++ b/components/email/email-composer.tsx @@ -647,6 +647,11 @@ export function EmailComposer({ if (nextHtml !== currentHtml) { editor.commands.setContent(nextHtml, { emitUpdate: true }); } + // Intentionally keyed to the signature-relevant fields plus the internal + // prev*Ref guards above; depending on the whole `signatureIdentity` object + // would re-run on unrelated identity-field changes and re-splice the + // signature into the live editor. + // eslint-disable-next-line react-hooks/exhaustive-deps }, [signatureIdentity?.id, signatureIdentity?.htmlSignature, signatureIdentity?.textSignature, signatureSeparatorEnabled, signaturePosition, mode, plainTextMode]); useEffect(() => { diff --git a/components/email/email-viewer.tsx b/components/email/email-viewer.tsx index c18a19d3..239c86f8 100644 --- a/components/email/email-viewer.tsx +++ b/components/email/email-viewer.tsx @@ -1331,6 +1331,10 @@ export function EmailViewer({ }, markAsReadDelay); return () => clearTimeout(timeout); + // Keyed to email id + $seen only: depending on the whole `email` object + // would reset the mark-as-read delay timer whenever any unrelated email + // field updates (e.g. a background re-fetch). + // eslint-disable-next-line react-hooks/exhaustive-deps }, [email?.id, email?.keywords?.$seen, onMarkAsRead]); // Reset external content permission and quick reply when email changes @@ -2260,6 +2264,11 @@ export function EmailViewer({ })); return [...jmapAttachments, ...tnefExtracted, ...embeddedExtracted]; + // The memo derives only from `email.attachments` (findCalendarAttachment + // scans that array); depending on the whole `email` object would rebuild the + // attachment list — and its downstream layout measurement — on every email + // field change. + // eslint-disable-next-line react-hooks/exhaustive-deps }, [email?.attachments, smimeDecryptedAttachments, tnefHtml, tnefText, tnefAttachments, embeddedEmailUnwrapped, embeddedEmailAttachments, calendarInvitationParsingEnabled, hideInlineImageAttachments]); // Measure attachment chips in the below-header row to determine how many fit @@ -2968,7 +2977,7 @@ export function EmailViewer({ ${wordHtmlCSS} ${darkModeCSS} ${effectiveEmailContent.html}`; - }, [effectiveEmailContent.html, effectiveEmailContent.isHtml, isDark, emailHasNativeDarkMode]); + }, [effectiveEmailContent.html, effectiveEmailContent.isHtml, effectiveEmailContent.hasStyleTag, isDark, emailHasNativeDarkMode]); // Imperatively restore blocked external content inside the iframe document. // Avoids re-rendering the iframe srcDoc (which would reload and flash) when @@ -3414,6 +3423,9 @@ export function EmailViewer({ if (autoMdnRef.current === email.id) return; autoMdnRef.current = email.id; sendReadReceiptNow(true).catch(() => { autoMdnRef.current = null; }); + // email is already captured via email?.id and sendReadReceiptNow (which + // depends on `email`); the autoMdnRef guard prevents a double send. + // eslint-disable-next-line react-hooks/exhaustive-deps }, [readReceiptResponse, shouldOfferReadReceipt, email?.id, sendReadReceiptNow]); // Show loading skeleton while email is being fetched diff --git a/components/pro/pro-compose-tab-body.tsx b/components/pro/pro-compose-tab-body.tsx index 49dd46d1..a175e157 100644 --- a/components/pro/pro-compose-tab-body.tsx +++ b/components/pro/pro-compose-tab-body.tsx @@ -25,11 +25,9 @@ export function ProComposeTabBody({ tabId, data }: ProComposeTabBodyProps) { const t = useTranslations(); const client = useAuthStore((s) => s.client); const sendEmail = useEmailStore((s) => s.sendEmail); - const fetchEmails = useEmailStore((s) => s.fetchEmails); const refreshCurrentMailbox = useEmailStore((s) => s.refreshCurrentMailbox); const fetchScheduledEmails = useEmailStore((s) => s.fetchScheduledEmails); const refreshScheduledMetadata = useEmailStore((s) => s.refreshScheduledMetadata); - const selectedMailbox = useEmailStore((s) => s.selectedMailbox); const isScheduledView = useEmailStore((s) => s.isScheduledView); const closeTab = useProTabStore((s) => s.closeTab); const updateTabTitle = useProTabStore((s) => s.updateTabTitle); @@ -116,7 +114,7 @@ export function ProComposeTabBody({ tabId, data }: ProComposeTabBodyProps) { console.error('Failed to send email:', error); toast.error(t('notifications.error_sending')); } - }, [client, sendEmail, fetchEmails, selectedMailbox, closeTab, data.sourceEmailId, data.mode, t, handleScheduledSendCreated]); + }, [client, sendEmail, closeTab, data.sourceEmailId, data.mode, t, handleScheduledSendCreated, refreshCurrentMailbox]); const handleClose = useCallback(() => { closeTab(tabIdRef.current); diff --git a/hooks/use-mailbox-drop.ts b/hooks/use-mailbox-drop.ts index 6ccf363e..4954423c 100644 --- a/hooks/use-mailbox-drop.ts +++ b/hooks/use-mailbox-drop.ts @@ -222,7 +222,7 @@ export function useMailboxDrop({ mailbox, onDropComplete, onSuccess, onError }: } finally { endDrag(); } - }, [client, mailbox, mailboxes, isValidTarget, moveEmailsToMailbox, crossAccountMoveEmails, draggedEmails, selectedEmailIds, clearSelection, refreshCurrentMailbox, endDrag, onDropComplete, onSuccess, onError]); + }, [client, mailbox, mailboxes, isValidTarget, moveEmailsToMailbox, crossAccountMoveEmails, draggedEmails, sourceMailboxId, selectedEmailIds, clearSelection, refreshCurrentMailbox, endDrag, onDropComplete, onSuccess, onError]); const valid = isValidTarget();