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
+55
View File
@@ -125,6 +125,61 @@ test("a host that clears the status hides the toast without waiting", () => {
assert.equal(clock.count(), 0, "a cleared message leaves no timer behind");
});
/// improvise-1x9h: a message that stands beside an open editor is the user's
/// until the editor closes, not the timer's. Evidence: clients.md.
test("a standing message outlives the timer and goes with its editor", () => {
const clock = fakeClock();
const shown = [];
let visible = false;
const toasts = createToasts({
onShow: (t) => {
shown.push(t);
visible = true;
},
onHide: () => {
visible = false;
},
ttlMs: 6000,
setTimer: clock.setTimer,
clearTimer: clock.clearTimer,
});
toasts.offer("Formula error", true);
assert.equal(clock.count(), 0, "a standing message arms no timer");
clock.fire();
assert.equal(visible, true, "the refusal hid while its editor was open");
toasts.offer("Formula error", true);
assert.deepEqual(shown, ["Formula error"], "a repaint is not a second event");
toasts.offer("Formula error", false);
assert.equal(visible, false, "the refusal outlived its editor");
assert.equal(clock.count(), 0, "leaving hides now, not on a timer");
toasts.offer("Formula error", true);
assert.equal(visible, false, "a message that stopped standing came back");
});
/// The other direction: a message that does not stand is on the timer exactly
/// as before (improvise-1x9h).
test("a message that does not stand keeps its timer", () => {
const clock = fakeClock();
let visible = false;
const toasts = createToasts({
onShow: () => {
visible = true;
},
onHide: () => {
visible = false;
},
ttlMs: 6000,
setTimer: clock.setTimer,
clearTimer: clock.clearTimer,
});
toasts.offer("Formula added", false);
assert.equal(clock.count(), 1);
clock.fire();
assert.equal(visible, false);
});
test("an empty status on the first frame shows nothing", () => {
// The common case: every repaint before any command offers "". A feed that
// treated the first offer as new would flash an empty toast on load.