Fix: stop resurrecting deleted rows in the mailbox refresh merge

Fixes #592.

refreshCurrentMailbox merges the refreshed first page with the already
loaded list, appending existing entries beyond a cutoff. That cutoff
was derived from the refreshed list's length - so whenever a folder
shrank, the fresh page was shorter than the stale list and the loop
re-appended the deleted rows from stale local state, despite the
comment right above promising the opposite.

The visible result is the reported bug: after sending a draft, the
Drafts view keeps showing a ghost row for the already-destroyed draft.
The send actually succeeded - resending the ghost delivers the mail
again, which we reproduced with a live JMAP trace: four successful
submissions, an empty server-side Drafts folder, a notFound ghost id,
and five delivered copies. Deriving the cutoff from the page size
fixes the shrink case while preserving the merge's intent for
arrivals and loaded deeper pages; regression tests cover all three
shapes.

Also surface post-send filing failures instead of dropping them, as
flagged in 4dc76bbb's follow-up note: a rejected onSuccessUpdateEmail
patch or old-draft destroy now logs the server's error details and
returns a filingError on SendEmailResult, and the UI shows a warning
toast (all 23 locales) so a stale draft row is never again mistaken
for a failed send. A plugin veto of the send leaves a debug trace.
This commit is contained in:
dealerweb
2026-07-23 13:26:05 +02:00
parent e442c55931
commit af2b00dd35
29 changed files with 197 additions and 27 deletions
+6 -1
View File
@@ -1848,7 +1848,12 @@ export function EmailComposer({
inReplyTo: threadingHeaders?.inReplyTo?.[0],
};
const sendAllowed = await emailHooks.onBeforeEmailSend.intercept(sendablePreview);
if (!sendAllowed) return;
if (!sendAllowed) {
// A plugin vetoed the send (it is expected to show its own UI).
// Leave a trace so a silent no-op send is diagnosable (#592).
debug.log('email', 'Send aborted by an onBeforeEmailSend plugin handler');
return;
}
// Hand off to a crypto plugin (S/MIME, PGP, …) if one wants to take over
// the send: it builds raw MIME, signs/encrypts, and submits via