From bb269816f8eb8502801619beaf017cb9eaffee81 Mon Sep 17 00:00:00 2001 From: RECTOR Date: Sat, 29 Aug 2026 20:17:41 +0700 Subject: [PATCH] =?UTF-8?q?fix(panel):=20remove=20dead=20Input=20onEscape?= =?UTF-8?q?=20callbacks=20=E2=80=94=20Escape=20cancels=20(#63)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The panel's handleInput intercepts Escape in every modal branch BEFORE the active pi-tui Input sees it, so pi-tui's Input.onEscape never fired — all 15 assignments (link/task/lc*/sched*/resume/steer/tiers/wfPrompt) were dead code, and several promised 'Escape accepts the default' semantics that never existed. - remove all 15 dead onEscape callbacks - remove the unreachable escape check in the lcRevising branch (same dead-code class — the panel-level intercept resolves the checkpoint as abort first) - document the Escape policy in handleInput: Escape cancels; defaults via Enter-on-blank. Caveat documented: ctrl+c also matches tui.select.cancel but is forwarded (Input ignores control chars) — a no-op since this PR; pre-PR it could fire the execute-type onEscape callbacks (accidental launches), so the removal is also a safety improvement, not a pure no-op. - fix the stale SPEC-6-5 comment on lcCwdInput - align SPEC-6-5 spec §3.5 to the actual behavior: Escape cancels the run Default paths unchanged: blank-Enter already accepted defaults on every step that had an accept-default onEscape (name→'default', cwd→parentCwd, link→undefined, fork task→run.task). Closes #63 --- ...026-08-08-spec-6-5-cwd-isolation-design.md | 2 +- src/panel/fleet-panel.ts | 28 +++++++------------ 2 files changed, 11 insertions(+), 19 deletions(-) diff --git a/docs/superpowers/specs/2026-08-08-spec-6-5-cwd-isolation-design.md b/docs/superpowers/specs/2026-08-08-spec-6-5-cwd-isolation-design.md index 3239ff8..35bd457 100644 --- a/docs/superpowers/specs/2026-08-08-spec-6-5-cwd-isolation-design.md +++ b/docs/superpowers/specs/2026-08-08-spec-6-5-cwd-isolation-design.md @@ -107,7 +107,7 @@ task → name → cwd (Input prefilled with deps.parentCwd; Enter to accept, or The `cwd` Input: - prefilled/default = `deps.parentCwd` (Enter accepts the session cwd) - a typed path is resolved + validated (exists + dir) before `executeLifecycleRun` -- Escape at the cwd step → run with the default (session cwd), mirroring the existing **name step's** Escape-accepts-default pattern (`this.lcNameInput.onEscape = () => executeLifecycleRun(task, "default")`); Escape at the task or name step still cancels the run as today +- Escape at any step **cancels the run** — aligned with the shipped behavior (#63: the panel's `handleInput` intercepts Escape before the Input's `onEscape` can fire, so the original "Escape-accepts-default" intent was never reachable; the dead `onEscape` callbacks were removed). Accept-default is **Enter-on-blank**: the name step blanks to `"default"` and the cwd step blanks to the session cwd - the chosen `cwd` becomes the **entry-point cwd** passed to `runLifecycle` This makes the run's scope **visible per run** (the #47 lesson: surface what's implicit) and gives the human a cross-cwd lever without a modal/config. diff --git a/src/panel/fleet-panel.ts b/src/panel/fleet-panel.ts index c4c3e9e..3fab376 100644 --- a/src/panel/fleet-panel.ts +++ b/src/panel/fleet-panel.ts @@ -425,10 +425,8 @@ export class FleetPanel extends Container { this.linkInput.onSubmit = (todoIdRaw: string) => { void this.executeRun(agentName, task.trim(), todoIdRaw.trim() || undefined); }; - this.linkInput.onEscape = () => { void this.executeRun(agentName, task.trim(), undefined); }; this.renderShell(); }; - this.taskInput.onEscape = () => this.cancelRun(); this.runMode = true; this.renderShell(); } @@ -492,6 +490,12 @@ export class FleetPanel extends Container { } handleInput(data: string): void { + // Escape policy (#63): every modal branch below intercepts Escape BEFORE the active Input + // sees it, so pi-tui's Input.onEscape never fires in this panel — Escape always cancels + // the active flow. Defaults are accepted via Enter-on-blank ("blank=default" prompts). + // Caveat: ctrl+c also matches pi-tui's tui.select.cancel but is NOT intercepted here — + // it forwards to the Input, which ignores control characters (silent no-op). An onEscape + // callback re-added later would fire on ctrl+c but never on Escape — do not re-add. if (this.infoAgent) { if (matchesKey(data, "escape")) { this.infoAgent = null; this.renderShell(); } return; @@ -811,13 +815,13 @@ export class FleetPanel extends Container { this.pendingCheckpoint = null; this.renderShell(); }; - this.lcReviseInput.onEscape = () => { this.lcRevising = false; this.lcReviseInput = null; this.renderShell(); }; this.renderShell(); return; } } if (this.lcRevising && this.lcReviseInput) { - if (matchesKey(data, "escape")) { this.lcRevising = false; this.lcReviseInput = null; this.renderShell(); return; } + // Escape never reaches here — the panel-level intercept above resolves the pending + // checkpoint as abort + closes the panel first (#63). Only printable input forwards. this.lcReviseInput.handleInput(data); this.invalidate(); return; @@ -842,13 +846,10 @@ export class FleetPanel extends Container { const lcName = name.trim() || "default"; this.executeScheduleAdd(task.trim(), expr.trim(), lcName); }; - this.schedNameInput.onEscape = () => { this.executeScheduleAdd(task.trim(), expr.trim(), "default"); }; this.renderShell(); }; - this.schedExprInput.onEscape = () => this.cancelScheduleAdd(); this.renderShell(); }; - this.schedTaskInput.onEscape = () => this.cancelScheduleAdd(); this.schedRunMode = true; this.renderShell(); } @@ -892,7 +893,6 @@ export class FleetPanel extends Container { if (!followUp.trim()) { this.cancelResume(); return; } void this.executeResume(run, followUp.trim()); }; - this.resumeInput.onEscape = () => this.cancelResume(); this.resumeMode = true; this.renderShell(); } @@ -916,7 +916,6 @@ export class FleetPanel extends Container { if (!text.trim()) { this.cancelSteer(); return; } void this.executeSteer(run.runId, text.trim()); }; - this.steerInput.onEscape = () => this.cancelSteer(); this.steerMode = true; this.renderShell(); } @@ -969,7 +968,6 @@ export class FleetPanel extends Container { } this.tiersInput = new Input(); this.tiersInput.onSubmit = (value: string) => { void this.executeTiersEdit(value, phase); }; - this.tiersInput.onEscape = () => this.cancelTiersEdit(); this.tiersEditPhase = phase; this.renderShell(); } @@ -995,7 +993,6 @@ export class FleetPanel extends Container { } this.cancelWorkflowRun(); }; - this.wfPromptInput.onEscape = () => this.cancelWorkflowRun(); this.wfRunMode = true; this.renderShell(); } @@ -1097,10 +1094,8 @@ export class FleetPanel extends Container { this.linkInput.onSubmit = (todoIdRaw: string) => { void this.executeFork(run.agent, finalTask, todoIdRaw.trim() || undefined, run.runId); }; - this.linkInput.onEscape = () => { void this.executeFork(run.agent, finalTask, undefined, run.runId); }; this.renderShell(); }; - this.taskInput.onEscape = () => this.cancelRun(); this.runMode = true; this.renderShell(); } @@ -1134,19 +1129,16 @@ export class FleetPanel extends Container { const lcName = name.trim() || "default"; this.lcPhase = "cwd"; this.lcCwdInput = new Input(); - // SPEC-6-5: 3rd input step — the dispatch cwd. Prefilled with the session cwd; Enter - // accepts it, Escape accepts the default (mirrors the name step's Escape-accepts-default). + // SPEC-6-5: 3rd input step — the dispatch cwd. Enter accepts the session cwd (blank) + // or a typed path; Escape cancels the run (panel-level intercept — #63). this.lcCwdInput.onSubmit = (cwd: string) => { const picked = cwd.trim() || this.deps.parentCwd; void this.executeLifecycleRun(task.trim(), lcName, picked); }; - this.lcCwdInput.onEscape = () => { void this.executeLifecycleRun(task.trim(), lcName, this.deps.parentCwd); }; this.renderShell(); }; - this.lcNameInput.onEscape = () => { void this.executeLifecycleRun(task.trim(), "default", this.deps.parentCwd); }; this.renderShell(); }; - this.lcTaskInput.onEscape = () => this.cancelLifecycleRun(); this.lcRunMode = true; this.renderShell(); }