A message beside its open editor stands in the browser too (improvise-1x9h)

The toast hid every status after six seconds, so a refusal the user was
still fixing vanished with its prompt open: su4u's bug on a timer. The
browser now asks the TUI's question. EditorSession moves into
improvise-protocol, ClientState records which editor a status was
written in, and RenderModel::status_stands tells the toast to hold it
until that editor closes.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VdfEGXA7aJ6ftMsCc9PJze
This commit is contained in:
Edward Langley
2026-09-23 18:08:26 -07:00
parent 09db8eb0cd
commit 9eb83538fe
12 changed files with 389 additions and 45 deletions
+18 -5
View File
@@ -16,7 +16,10 @@ export const TOAST_MS = 6000;
/** `ViewState::status_msg` is STATE, not an event: a repaint re-offers the
* string that is already on screen, so dismissal keys off message IDENTITY
* and never off a timer alone. What that costs: context/code/clients.md. */
* and never off a timer alone. What that costs: context/code/clients.md.
*
* `stands` is Rust's answer (`RenderModel::status_stands`): while it holds the
* message is on no timer, and when it stops the message goes (improvise-1x9h). */
export function createToasts({
onShow,
onHide,
@@ -28,12 +31,21 @@ export function createToasts({
// mechanism: a repaint offering it again is recognised and dropped.
let offered = "";
let timer = null;
let standing = false;
return {
offer(text) {
offer(text, stands = false) {
const next = text ?? "";
if (next === offered) return;
if (next === offered) {
// Standing ends once: the editor it belonged to has closed.
if (standing && !stands) {
standing = false;
onHide();
}
return;
}
offered = next;
standing = next !== "" && stands;
clearTimer(timer);
timer = null;
if (next === "") {
@@ -41,6 +53,7 @@ export function createToasts({
return;
}
onShow(next);
if (standing) return;
timer = setTimer(() => {
timer = null;
onHide();
@@ -349,8 +362,8 @@ export function createShell({
setShell(text) {
onShell(text);
},
setModel(text) {
onModel(text);
setModel(text, stands = false) {
onModel(text, stands);
},
/** A peer that never answers the handshake. Deliberately about the *first
* frame*, not either peer's own failure modes — clients.md. */