From b62dc4a6368019d5adfe1a1d9eac204309f0cb11 Mon Sep 17 00:00:00 2001 From: Aiden Cline <63023139+rekram1-node@users.noreply.github.com> Date: Mon, 10 Aug 2026 14:26:29 -0500 Subject: [PATCH] fix(tui): show empty state when a flat picker has no matches (#41585) --- packages/tui/src/ui/dialog-select.tsx | 2 +- .../tui/test/cli/tui/dialog-select.test.tsx | 30 +++++++++++++++++++ 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/packages/tui/src/ui/dialog-select.tsx b/packages/tui/src/ui/dialog-select.tsx index 66600cc355..fbcf9da936 100644 --- a/packages/tui/src/ui/dialog-select.tsx +++ b/packages/tui/src/ui/dialog-select.tsx @@ -212,7 +212,7 @@ export function DialogSelect(props: DialogSelectProps) { const flatten = createMemo(() => props.flat && store.filter.length > 0) const grouped = createMemo<[string, DialogSelectOption[]][]>(() => { - if (flatten()) return [["", filtered()]] + if (flatten()) return filtered().length ? [["", filtered()]] : [] const result = pipe( filtered(), groupBy((x) => x.category ?? ""), diff --git a/packages/tui/test/cli/tui/dialog-select.test.tsx b/packages/tui/test/cli/tui/dialog-select.test.tsx index 574c7017a2..b0c96d05c8 100644 --- a/packages/tui/test/cli/tui/dialog-select.test.tsx +++ b/packages/tui/test/cli/tui/dialog-select.test.tsx @@ -87,6 +87,7 @@ async function mountSelect( initial: DialogSelectOption[], current?: string, focusCurrent?: boolean, + select?: { flat?: boolean }, ) { const state = path.join(root, "state") await mkdir(state, { recursive: true }) @@ -124,6 +125,7 @@ async function mountSelect( options={options()} current={current} focusCurrent={focusCurrent} + flat={select?.flat} onMove={(option) => moved.push(option.value)} onSelect={(option) => selected.push(option.value)} /> @@ -367,6 +369,34 @@ test("keeps the current option selected when options reorder", async () => { } }) +test("shows no-match and still closes after a flat filter goes empty", async () => { + await using tmp = await tmpdir() + const select = await mountSelect( + tmp.path, + [ + { title: "models.dev", value: "models.dev", category: "Projects" }, + { title: "opencode2", value: "opencode2", category: "Projects" }, + ], + undefined, + undefined, + { flat: true }, + ) + + try { + await select.app.waitForFrame((frame) => frame.includes("models.dev")) + await select.app.mockInput.typeText("models") + await select.app.waitForFrame((frame) => frame.includes("models.dev") && !frame.includes("opencode2")) + await select.app.mockInput.typeText(" missing") + await select.app.waitForFrame((frame) => frame.includes("No results found")) + expect(select.app.captureCharFrame()).not.toContain("models.dev") + + select.app.mockInput.pressEscape() + await select.app.waitForFrame((frame) => !frame.includes("Mutable options") && !frame.includes("No results found")) + } finally { + select.app.renderer.destroy() + } +}) + test("keeps the first row selected when current is only a marker", async () => { await using tmp = await tmpdir() const project = { title: "project", value: "project" }