fix(tui): remounted guarded draft editor when ask surface is restored

A failed async submission can restore the draft while a nested ask prompt
(note/custom answer) is open, re-blocking the input guard; restoreAskDialog
mounted only the ask component, routing guarded input to an unmounted
editor. Restore now mirrors the initial presentation and remounts the
draft editor whenever the guard exists.
This commit is contained in:
can1357
2026-07-27 04:35:52 +02:00
parent 2760158420
commit c2e07a2028
2 changed files with 39 additions and 0 deletions
@@ -201,6 +201,40 @@ describe("ExtensionUiController editor UI", () => {
expect(harness.editorContainer.children).toEqual([harness.editor]);
});
it("remounts the draft editor when the ask surface is restored after a nested prompt (#6738)", async () => {
const harness = makeHarness();
harness.editor.setText("half typed prompt");
const questions: ExtensionAskDialogQuestion[] = [
{ id: "confirm", question: "Continue?", options: [{ label: "Yes" }, { label: "No" }] },
];
harness.controller.showAskDialog(questions);
const ask = harness.editorContainer.children[0];
expect(ask).toBeInstanceOf(AskDialogComponent);
expect(harness.editorContainer.children).toEqual([ask, harness.editor]);
// Draft submitted: the guard lifts and ask controls take input; open the
// note prompt, which swaps the container to the nested editor.
harness.editor.setText("");
ask?.handleInput?.("n");
const promptEditor = harness.editorContainer.children[0];
expect(promptEditor).not.toBe(ask);
// A failed async submission restores the draft while the nested prompt is
// open, re-blocking the guard.
harness.editor.setText("half typed prompt");
// Cancelling the nested prompt restores the ask surface; the draft editor
// must be remounted so routed input lands on a visible surface.
promptEditor?.handleInput?.("\x1b");
expect(harness.editorContainer.children).toEqual([ask, harness.editor]);
// The dialog's prompt-active latch clears when the awaited onPrompt
// promise settles; yield a microtask before routing the next key.
await Promise.resolve();
ask?.handleInput?.("!");
expect(harness.editor.getText()).toBe("half typed prompt!");
});
it("bridges addAutocompleteProvider factories to the interactive mode context (#4919)", async () => {
const harness = makeHarness();
const ui = await harness.init();
@@ -642,6 +642,11 @@ export class ExtensionUiController {
if (closed || !askDialog) return;
this.ctx.editorContainer.clear();
this.ctx.editorContainer.addChild(askDialog);
// Keep the draft editor mounted beneath the restored ask, matching the
// initial presentation: the guard re-blocks whenever the draft is
// non-empty (e.g. a failed submit restored its text while a nested
// prompt was open), and routed input must land on a visible surface.
if (inputGuard) this.ctx.editorContainer.addChild(this.ctx.editor);
this.ctx.ui.setFocus(askDialog);
this.ctx.ui.requestRender();
};