fix(remote-questions): cancel local TUI when remote answer wins the race

showInterviewRound now accepts an AbortSignal via opts.signal. When the
remote channel wins the race, controller.abort() closes the local TUI
modal instead of leaving an orphaned interactive prompt capturing input.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Jeremy 2026-04-08 19:08:33 -05:00
parent b619373f0d
commit 74fb4913b1
2 changed files with 14 additions and 1 deletions

View file

@ -254,7 +254,7 @@ export default function AskUserQuestions(pi: ExtensionAPI) {
const raceResult = await raceRemoteAndLocal(
() => tryRemoteQuestions(params.questions, raceSignal),
() => showInterviewRound(params.questions, {}, ctx as any),
() => showInterviewRound(params.questions, { signal: raceSignal }, ctx as any),
raceController,
params.questions,
);

View file

@ -80,6 +80,12 @@ export interface InterviewRoundOptions {
* Label for the Esc-confirm overlay header. Defaults to "End interview?".
*/
exitHeadline?: string;
/**
* Optional AbortSignal to cancel the interview externally (e.g. when racing
* against a remote question channel). When aborted, the TUI closes and the
* promise resolves with an empty answers object.
*/
signal?: AbortSignal;
/**
* Text for the "exit" hint shown in the review screen footer and exit confirm overlay.
* Defaults to "end interview".
@ -207,6 +213,13 @@ export async function showInterviewRound(
let exitCursor = 0; // 0 = keep going (default), 1 = end interview
let cachedLines: string[] | undefined;
// External cancellation (e.g. remote channel won the race)
if (opts.signal) {
const onAbort = () => done({ endInterview: false, answers: {} });
if (opts.signal.aborted) { onAbort(); }
else { opts.signal.addEventListener("abort", onAbort, { once: true }); }
}
// Editor is created once; editorTheme comes from the design system
const editorRef = { current: null as Editor | null };