fix(compose): a draft that died at thirty seconds, under the quote, in silence

Three faults in the writing help, all of them things that only appear once it is
actually being used.

PHP's max_execution_time is 30 seconds for the web SAPI while the model call is
allowed 120, so the limit always belonged to the wrong side of the request. A
writing model that has fallen out of the keep-alive window needs about thirteen
seconds to be read off disk BEFORE the first token exists, and a reply worth
reading is another twenty or thirty after it — so the limit fired mid-generation
and the reader watched the text stop halfway:

    MaxExecutionTimeError: Maximum execution time of 30 seconds exceeded
    at CurlResponse.php line 370

The worst possible place for it to land, too: inside the HTTP client's read, so
the unwinding that cancels the model call and records the row never happened and
Ollama carried on generating for a reader who had already been shown an error.
The bound now comes from the pieces that can actually enforce it — the upstream
timeout, and connection_aborted() on the next write.

The answer went in at the end of the editor, which in a reply is UNDERNEATH the
quoted original: an answer nobody scrolls to, sitting below the message it was
answering. It goes above the quote now, which is where a person's cursor already
is when they start typing a reply. Plain text too, where the boundary is the
first `>` line less its attribution line — inserting between "On … wrote:" and
the quotation splits it in half.

And the wait had nothing moving in it. There is nothing honest to put in a
progress bar, because the host reports no progress at all while it loads, so a
still sentence was all there was and it read as a window that had hung. Three
dots that breathe say "working" and claim nothing about how far along it is,
which is exactly as much as we know. They stop at the first token, where
arriving text says it better, and they hold still but stay visible under
prefers-reduced-motion.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Paul LütznerandClaude Opus 5 committed 2026-08-27 01:50:07 +02:00
1 parent 6987166a25
commit 2d1586cb68
5 files changed
+230 -60

No files matched your search

+17
View File
@@ -1100,6 +1100,23 @@ Variables: form, message, ctx (an App\Domain\DTO\Mail\ComposeContext)
z-30 popover flex flex-col">
<div class="flex items-start gap-2 px-3 py-2">
{# Something moving while nothing is arriving.
Between the click and the first token there can be
thirteen seconds of a 30B model being read off disk,
and Ollama reports no progress through it — so there
is nothing to fill a bar with and a still sentence
reads as a hung window. Three dots that breathe say
"working" without claiming to know how far along it
is. aria-hidden: the status line beside it already
announces the state in words, and a screen reader has
no use for decoration. Held still under
prefers-reduced-motion, where it stays visible as
three plain dots. #}
<span data-compose--ai-assist-target="pending"
hidden
aria-hidden="true"
class="ai-pending mt-[3px] shrink-0"><i></i><i></i><i></i></span>
<div class="min-w-0 flex-1">
{# `role="status"` with aria-live so the STATE TRANSITIONS
are announced rather than only drawn. Only transitions: