fix(tui): confirm a kill with y, and make the dialog say what it would kill

Killing demanded the session's NAME typed out in full. That is the right
ceremony for dropping a production database and the wrong one for closing a
pane you are looking at; the beta tester's verdict was "thats stupid, just make
me type Y to confirm". `x` then `y` is already two deliberate keystrokes on a
row the user selected, and the conversation lives in its transcript, which a
kill does not touch.

Everything that is not `y` CANCELS rather than being ignored, so a stray key
closes the dialog instead of leaving a destructive prompt armed and waiting for
whatever gets typed next. Enter cancels too: it is the key most likely to be
hit by reflex, and this is the one dialog that destroys something.

⚠️ Found while verifying the new dialog: it did not name the session. The label
was computed as `row.session.name ?? id.slice(0, 8)`, and `??` falls back only
on null or undefined, so every session the server left with an EMPTY name — all
of them, until the TUI started naming its own — sailed through and the box read
"Kill ?". A destructive prompt that cannot say what it will destroy is worse
than no prompt, and it is now a single keystroke. The caller passes the same
label the LIST shows, so the dialog names the row in front of the user.

The typed-name machinery goes with it: TuiConfirmState.typed, setConfirmInput(),
confirmAccepts() and the 'typing'/'reject' steps are all removed rather than
left as unreachable branches.
This commit is contained in:
Codeman maintainer
2026-08-22 14:13:58 +02:00
parent 7b1150ca4f
commit b2ac6c1bd9
8 changed files with 75 additions and 84 deletions
+9 -9
View File
@@ -442,22 +442,22 @@ export class TuiModelStore implements TuiRenderModel {
* for every caller: a second copy here answered the same question differently
* (it refused the id prefix a mux name carries) and nothing consulted it.
*/
beginConfirmKill(row: TuiRow): void {
beginConfirmKill(row: TuiRow, label: string): void {
this.confirm = {
sessionId: row.session.sessionId,
name: row.session.name ?? row.session.sessionId.slice(0, 8),
typed: '',
// ⚠️ Passed in, not derived here. `row.session.name ?? id.slice(0,8)`
// used to compute it, and `??` falls back only on null/undefined: a
// session whose name is the EMPTY STRING (every session the server did
// not name) sailed through it and the dialog read "Kill ?". A destructive
// prompt that cannot say what it is about to destroy is worse than no
// prompt, and it is now one keystroke. The caller passes the same label
// the LIST shows, so the dialog names the row the user is looking at.
name: label,
};
this.mode = 'confirm-kill';
this.touch();
}
setConfirmInput(typed: string): void {
if (!this.confirm) return;
this.confirm = { ...this.confirm, typed };
this.touch();
}
/** Drop whatever overlay owns the keyboard and go back to the list. */
closeOverlay(): void {
this.confirm = null;