Files
pl_mail/templates/compose/_undo_too_late.html.twig
Paul LütznerandClaude Opus 5 7065d2a192 fix(compose): "Message sent." was said two seconds before the send was attempted
A send is held for ten seconds so it can be called off. The browser
marked the end of the CANCEL window — eight seconds — by asking the
server to tidy up, and that answer carried "Message sent." So the
confirmation preceded the attempt by two seconds, every time, for
everybody.

And nothing ever corrected it. All three senders turn a failure into a
bare `false`, which SendMessageHandler treated as handled: no retry, no
failed-transport row, nothing. SmtpMailSender goes further and binds the
transport exception without using it, so a hard SMTP rejection left zero
trace anywhere in the system. The user was told the mail had gone, and
it had not.

Now the eight-second mark only tidies, and the outcome is published from
the worker when there is one — success or failure, over the live-updates
stream that is already there. The failure toast is the case that had no
surface at all.

It publishes RENDERED html, because the alternative is a toast built in
JavaScript, which is a second copy of _toast.html.twig drifting from the
first.

Best effort, and honestly so: with the hub down there is no toast, which
is a confirmation missing rather than a confirmation that lies.

WHAT THE E2E SUITE CAN AND CANNOT SHOW

It cannot show the toast. The test app runs
MESSENGER_TRANSPORT_DSN=in-memory://, so a send dispatched by a browser
request is discarded when the request ends and no worker can pick it up
— which also means those specs were only ever asserting the premature
claim. They now pin the half that was wrong: that nothing announces an
outcome it does not have. The publishing side is covered by
SendOutcomeNotifierTest.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-25 20:49:19 +02:00

47 lines
2.3 KiB
Twig

{#
compose/_undo_too_late.html.twig
─────────────────────────────────────────────────────────────────────────────
The cancel arrived after SendMessageHandler had already claimed the message.
This template exists because the alternative was lying. Every undo used to
answer with the dock-undo stream — "send cancelled", window restored — whether
or not the cancel had actually taken, and a cancel clicked in the last fraction
of the hold does not take. The user was told the one thing that was not true
about the one action that cannot be reversed.
So: an ERROR toast, not an info one, because something the user asked for did
not happen; and no window, because there is no draft to reopen. Restoring the
composer here would put an editable copy of an already-sent mail on screen, and
the next autosave would file it as a draft standing beside the sent copy.
And the page is left exactly as a send that ran its course leaves it, by
including the very same partial: whatever window this cancel came from is
describing a message that has gone, so it closes, and the message takes its
place in the conversation. That used to be a bare "empty the dock", which was
right only because the inline composer had already been replaced by a
countdown bar before this could be reached. It is not any more — the window is
still open, still holding a copy of a mail that is on the wire — so saying
"too late" without also closing it would leave the user an editable draft of
something already sent.
Variables: message, thread (MessageThread|null), ctx
#}
<turbo-stream action="append" target="toast-region">
<template>
{% include '_partials/_toast.html.twig' with {
type: 'error',
message: 'toast.send_too_late'|trans,
extra: { hide_after: 8000 },
} only %}
</template>
</turbo-stream>
{# The error above IS this send's notification. Nothing has to be suppressed
any more: _sent.stream stopped claiming success, because at the moment it
runs nobody knows whether the send worked. #}
{% include 'compose/_sent.stream.html.twig' with {
message: message,
thread: thread,
ctx: ctx,
} only %}