diff --git a/packages/coding-agent/src/modes/interactive/interactive-mode.ts b/packages/coding-agent/src/modes/interactive/interactive-mode.ts index a596eea88..7b3b45ba9 100644 --- a/packages/coding-agent/src/modes/interactive/interactive-mode.ts +++ b/packages/coding-agent/src/modes/interactive/interactive-mode.ts @@ -2839,8 +2839,12 @@ export class InteractiveMode { // Save text from current editor before switching const currentText = this.editor.getText(); - this.disposeActiveSelector(); - this.editorContainer.clear(); + // An open extension dialog keeps the editor slot; the new editor is shown when the last dialog closes. + const dialogOpen = this.extensionDialogs.length > 0; + if (!dialogOpen) { + this.disposeActiveSelector(); + this.editorContainer.clear(); + } if (factory) { // Create the custom editor with tui, theme, and keybindings @@ -2898,7 +2902,7 @@ export class InteractiveMode { this.editor = this.defaultEditor; } - this.editorContainer.addChild(this.editor as Component); + if (!dialogOpen) this.editorContainer.addChild(this.editor as Component); if (this.activeStatusIndicator) { this.statusContainer.clear(); this.activeWorkingIndicatorEmbedded = this.setEditorWorkingStatusIndicator(this.activeStatusIndicator); @@ -2906,7 +2910,7 @@ export class InteractiveMode { this.statusContainer.addChild(this.activeStatusIndicator); } } - this.ui.setFocus(this.editor as Component); + if (!dialogOpen) this.ui.setFocus(this.editor as Component); this.ui.requestRender(); } diff --git a/packages/coding-agent/test/interactive-mode-extension-dialogs.test.ts b/packages/coding-agent/test/interactive-mode-extension-dialogs.test.ts index d296ff2b4..2a59f9189 100644 --- a/packages/coding-agent/test/interactive-mode-extension-dialogs.test.ts +++ b/packages/coding-agent/test/interactive-mode-extension-dialogs.test.ts @@ -19,6 +19,9 @@ type DialogMethods = { showExtensionCustom( factory: (tui: unknown, theme: unknown, keybindings: unknown, done: (result: T) => void) => Component, ): Promise; + setCustomEditorComponent( + factory: ((tui: unknown, theme: unknown, keybindings: unknown) => Component) | undefined, + ): void; }; function createContext(): DialogContext & DialogMethods { @@ -149,4 +152,24 @@ describe("InteractiveMode extension dialogs", () => { await expect(custom).resolves.toBe("kept"); expect(context.focused).toBe(context.editor); }); + + // Regression for #11431: replacing the editor while a dialog was open hid the dialog and left it unanswerable. + it("keeps an open dialog visible when the editor component is replaced", async () => { + const context = createContext(); + (context as unknown as { defaultEditor: unknown }).defaultEditor = context.editor; + const confirmed = context.showExtensionConfirm("Title", "Continue?"); + const customEditor: Component & { getText(): string; setText(text: string): void } = { + render: () => ["custom editor"], + invalidate: () => {}, + getText: () => "", + setText: () => {}, + }; + context.setCustomEditorComponent(() => customEditor); + + expect(visible(context)).toContain("Title"); + context.focused?.handleInput?.("\r"); + await expect(confirmed).resolves.toBe(true); + expect(context.focused).toBe(customEditor); + expect(context.editorContainer.children).toEqual([customEditor]); + }); });