A minibuffer refusal stands while the user fixes it (improvise-su4u)

#233 clears the status message at the start of every TUI keypress. In a
text-entry mode the message sits beside the prompt, and a formula that
does not parse keeps the editor open with the error. So the error
vanished on the first correcting key.

press now remembers the mode and the message. It clears the message as
before and dispatches the key. It puts the stale message back only if
the key left the user in the same text-entry mode and wrote no message
of its own. Editing keys keep the refusal on screen, a key's own message
still wins, and leaving the minibuffer (a successful commit or Esc)
drops it.

The mode check is observed, not predicted: which keys commit is the
keymap's knowledge, and the TUI loop does not copy it.

Rivals, each injected into press and restored: never restoring (#233),
restoring over a key's own message, and restoring whatever the mode each
fail a distinct step of the test.

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 11:33:00 -07:00
parent 202e7eac63
commit 2b1c5bf7ad
3 changed files with 136 additions and 3 deletions
+47
View File
@@ -424,3 +424,50 @@ is always drawn. So a browser user sees a message briefly and never loses the
hint. The TUI now does the same, on a keypress rather than a timer, because a
terminal status row has one line to share and a keypress is the event the TUI
already has.
### Except while a minibuffer stays open under it (improvise-su4u)
In a text-entry mode `minibuffer_line` draws the message to the right of the
prompt. Since improvise-v4hg, a formula that does not parse keeps the user in
`FormulaEdit` with the error. Under the plain rule, the error vanished on the
first correcting key: one Backspace and the user was fixing text they could no
longer see the complaint about. The same happened to any refusal that leaves
the user in a minibuffer.
**The rule: a key that leaves the user in the same text-entry mode, and says
nothing of its own, puts the stale message back.** `press` remembers the mode
and the message, clears the message as before, dispatches the key, and then
restores the message only if all three of these hold:
- the mode before the key had a minibuffer;
- the mode after the key is the same mode;
- the key wrote no message of its own.
So:
- editing keys keep the refusal on screen until the user acts on it;
- a key's own message always wins, so a second refused `Enter` shows the new
error, not the old one;
- leaving the minibuffer, by a successful commit or by `Esc`, drops it, exactly
as the plain rule does.
**Rejected alternatives.**
- *Expire only on the commit key.* That needs to know which keys commit, which
is the keymap's knowledge (`TextEntry::commit`), and a second copy of it in
the TUI loop. "The mode did not change" is observed rather than predicted,
like gotcha 31's `ReturnToNormalUnlessMoved`.
- *Never clear in a text-entry mode.* The message would then outlive `Esc` and
stand in the mode the user returns to, which is the cq3d bug again.
- *Compare the message after the key with the stale one.* A key that wrote the
same text would be indistinguishable from a key that wrote none. Clearing
first and restoring only an empty slot has no such ambiguity.
The mode test is equality of the whole `AppMode`. An edit that stayed in "a"
minibuffer but a different one does not count as staying. No shipped key does
that, but it would be leaving one prompt for another, and the message belonged
to the first.
**The browser is not covered by this.** There, `status_msg` is a toast that
hides after `TOAST_MS` (six seconds), and the minibuffer element draws only the
prompt and the text. A refusal therefore disappears on a timer, whatever the
user is doing. That is a different mechanism and has not been changed here.