feat(chat): stop an in-flight answer — Send becomes Stop, the partial is kept and persisted, the model stream is torn down

This commit is contained in:
2026-08-29 17:27:04 -04:00
parent 6bf7f456d4
commit 1a60ecbd8b
186 changed files with 1738 additions and 7796 deletions
+17 -2
View File
@@ -125,7 +125,23 @@
</div>
</section>
<form class="composer" id="composer">
<!-- Composer (phase 48, 2026-08-29, TODO.md L3): one button, two
roles — #send-btn reads "Send" when idle and morphs into the
enabled "Stop" control (.is-stop, rose treatment) while a turn
is in flight; a click or Enter in flight aborts the fetch
(AbortController in app.js) and the partial is kept + persisted
with the optional `stopped` marker, rendered as the .stopped-note
meta-row note by app.js — live and on restore. The spinner span
stays in the markup (contract marker, reduced-motion pin) but
never shows: the Stop label + treatment carry the in-flight
state.
`novalidate`: the input is cleared after send, so a `required`
constraint would silently block the Stop click/Enter — the
browser's constraint validation runs before the `submit` event
and would never reach handleSend's in-flight guard. The
`!text` guard in app.js is the real empty-input check (same
precedent as the tuning form's noValidate). -->
<form class="composer" id="composer" novalidate>
<label class="visually-hidden" for="message-input">Ask Brain of Reese a question</label>
<textarea
id="message-input"
@@ -133,7 +149,6 @@
rows="1"
placeholder="Ask me about the homelab…"
autocomplete="off"
required
></textarea>
<button type="submit" class="send-btn" id="send-btn">
<span class="spinner" aria-hidden="true" hidden></span>