diff --git a/tui/src/app.test.ts b/tui/src/app.test.ts index 58935f1fc..530a8b1c3 100644 --- a/tui/src/app.test.ts +++ b/tui/src/app.test.ts @@ -577,6 +577,42 @@ describe("NanobotTui layout", () => { expect(ui.skillMenu.visible).toBe(false) }) + test("does not queue unmatched skill text when Tab dismisses completion", async () => { + setup = await createRenderer({ width: 80, height: 24, screenMode: "alternate-screen" }) + const sent: string[] = [] + const app = mount(setup, sent) + const ui = app as unknown as { + ready: boolean + composer: TextareaRenderable + skillCandidates: SkillCandidate[] + skillMenu: { visible: boolean } + queuePreview: { root: { visible: boolean } } + } + app.accept({ event: "attached", chat_id: "chat" }) + await waitUntil(() => ui.ready) + ui.skillCandidates = [{ + name: "verify", + description: "Verify public behavior", + source: "builtin", + }] + + ui.composer.setText("start an active turn") + ui.composer.submit() + await waitUntil(() => sent.length === 1) + + await setup.mockInput.typeText("$missing") + expect(ui.skillMenu.visible).toBe(true) + setup.mockInput.pressTab() + + expect(ui.composer.plainText).toBe("$missing") + expect(ui.skillMenu.visible).toBe(false) + expect(ui.queuePreview.root.visible).toBe(false) + + app.accept({ event: "turn_end", chat_id: "chat", turn_id: "turn" }) + await Bun.sleep(1) + expect(sent).toEqual(["start an active turn"]) + }) + test("runs bang commands through the gateway without steering the agent", async () => { setup = await createRenderer({ width: 80, height: 24, screenMode: "alternate-screen" }) const sent: string[] = [] diff --git a/tui/src/app.ts b/tui/src/app.ts index 20fc8d19b..f298815d5 100644 --- a/tui/src/app.ts +++ b/tui/src/app.ts @@ -1627,6 +1627,8 @@ export class NanobotTui { this.skillMenu.hide() this.activeSkillQuery = null this.updateMeta() + key.preventDefault() + return } if (key.name === "escape") { this.skillMenu.hide()