Files
pl_mail/templates/compose/_inline_send.stream.html.twig
Paul LütznerandClaude Opus 5 35cf17f05d feat(compose): the send pill is the cancel, in every editor
A send used to answer by taking the window away and offering the way back
somewhere else — an Undo in a toast for the dock, a countdown bar in the
thread for an inline reply — plus a "Cancel" link that appeared beside the
pill while the request was out. Three surfaces for one act, none of them where
the user had just clicked, two of them in a window that no longer existed.

The window now stays exactly where it is for the cancel window. Its Send
button reads "Sending…" with living dots and "click to cancel" under it, and a
second click on that same button calls the send off. Both faces are stacked in
one grid cell and only toggle visibility, so the button cannot change size when
a send starts, when it ends, or on any frame of the animation.

The server's answer no longer re-renders anything: it hands the live window the
two URLs it cannot address by itself (compose/_sending.stream), and when the
offer expires the window asks ComposeController::sent() to file the message and
close it. A forward starts its own conversation, so the settle streams close the
frame unconditionally and touch the thread only when it is the thread on screen
— without that a forwarded mail left the composer reading "Sending…" for ever,
addressing ids that were nowhere in the page.

A toast now says the message went, because a composer that simply vanished was
the one moment in the flow with no feedback at all. Cancelling says nothing: the
draft coming back with everything in it is the answer, and the dock's old "Send
cancelled" toast was the last asymmetry between the two composers.

Also here, because they are the same window:

  • The inline composer gets a reduced header — the To field itself, typable,
    with From/Cc/Bcc/Subject folded behind the chevron. A forward opens needing
    exactly one thing and used to open with a label where that thing goes.
  • The suggestion panel opens on typing and on nothing else. `openOnFocus` is
    read at focus time and a forward's caret beats the code that sets it, so an
    empty query is refused at the moment the panel opens instead.
  • It overlays the body rather than reserving room, which in a header meant
    growing the header and shoving the editor down on every keystroke.
  • `mode` moved onto ComposeContext. As a render-time extra it was set by one
    action and lost by every other render, so a cancelled forward came back as a
    plain new message and asked whether you meant to send an empty mail.

compose/_send_toast, compose/_inline_send.stream, compose--undo-send and
compose--inline-send are kept, unwired and documented as such, while the
replacement is reviewed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-18 19:46:52 +02:00

37 lines
1.6 KiB
Twig

{#
compose/_inline_send.stream.html.twig
─────────────────────────────────────────────────────────────────────────────
Response to sending from the inline reply card. **NOT RENDERED ANY MORE.**
The other half of the pair the send rewrite replaced — where the dock got a
toast, the thread got a countdown bar in its reply zone, and both took the
composer away to do it. One surface now: the window stays and its Send pill is
the cancel. What this did on send, compose/_sent.stream.html.twig now does
when the cancel window runs out.
Kept, unwired, while that is reviewed. compose--inline-send goes with it.
Variables: message, thread (MessageThread|null), undoUrl, delay
#}
{% if thread is not null %}
{# Editing an existing draft: its row is already in the conversation, so
drop it first and re-append — the sent message belongs at the end. #}
<turbo-stream action="remove" target="thread_message_{{ message.id }}">
</turbo-stream>
<turbo-stream action="append" target="thread_messages_{{ thread.id }}">
<template>
{% include 'mail/_thread_message.html.twig' with { message: message, expanded: true, draft: false } only %}
</template>
</turbo-stream>
<turbo-stream action="replace" target="reply_zone_{{ thread.id }}">
<template>
{% include 'mail/_reply_zone.html.twig' with {
thread: thread,
sending: { undoUrl: undoUrl, delay: delay },
} only %}
</template>
</turbo-stream>
{% endif %}