fix(gate): guard the TUI-only "Esc" notice on ctx.mode, not ctx.hasUI

reLoop's "Esc non chiude il gate..." warning is terminal-specific. On pi 0.73
ctx.hasUI was false in RPC, so `if (ctx.hasUI)` effectively meant "TUI only". On
pi >=0.80 hasUI is true in RPC too (dialog-capable UI via the bridge), so the
notice leaked to the browser on any invalid gate response. Guard on
ctx.mode === "tui" to restore the original intent. The fake pi runtime gains
mode:"tui" so the roundtrip test still exercises the notice.

Audit of the other 5 ctx.ui.notify: left as-is. They are valid in both live
modes (TUI and RPC, both hasUI=true) and the gate cannot run headless
(emitAndWait needs a UI), so a guard would be dead code. Their dual-mode
rationalization belongs with the future present() work (tracked in the eval doc).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-04 14:21:57 +02:00
co-authored by Claude Opus 4.8
parent 0afa9a2f80
commit 1f9818c50f
2 changed files with 6 additions and 2 deletions
@@ -6,6 +6,7 @@ function createFakePi() {
const tools = new Map();
const ctx = {
hasUI: true,
mode: "tui",
cwd: "/tmp/fake-pi-session",
notifications: [],
uiCalls: [],
+5 -2
View File
@@ -316,7 +316,10 @@ export async function emitAndWait(ctx, descriptor) {
}
async function reLoop(ctx) {
if (ctx.hasUI)
// TUI-only: "Esc"/terminal navigation has no meaning in RPC (the browser
// renders widget buttons). Guard on ctx.mode, NOT ctx.hasUI — on pi >=0.80
// hasUI is true in RPC too, so this warning would leak to the frontend.
if (ctx.mode === "tui")
await ctx.ui.notify(
"Esc non chiude il gate: usa Torna indietro / Esci / Altro dalle opzioni.",
"warning",
@@ -648,7 +651,7 @@ export default function (pi) {
const resp = await emitAndWait(ctx, widget);
outcome = resolveConfirmOutcome(resp);
if (outcome.kind !== "unknown") break;
await ctx.ui.notify("Scegli «Conferma e prosegui» o «Rifiuta».", "warning");
await ctx.ui.notify("Scegli «Salva e procedi» o «Rifiuta».", "warning");
}
if (outcome.kind === "freetext")
return textResult(