diff --git a/src/tui-opentui/command-catalog.test.ts b/src/tui-opentui/command-catalog.test.ts index 9a2a0d869..4629c0471 100644 --- a/src/tui-opentui/command-catalog.test.ts +++ b/src/tui-opentui/command-catalog.test.ts @@ -6,7 +6,7 @@ import { } from "./command-catalog" describe("commandItemsFromRegistry", () => { - test("maps listCommands-shaped entries to `/`-prefixed labels", () => { + test("maps listCommands-shaped entries to name-only `/` labels", () => { const items = commandItemsFromRegistry([ { name: "tasks", description: "Show work list" }, { name: "clear", description: "Clear screen" }, @@ -14,13 +14,13 @@ describe("commandItemsFromRegistry", () => { expect(items).toEqual([ { id: "tasks", - label: "/tasks — Show work list", - keywords: ["tasks", "slash", "command"], + label: "/tasks", + keywords: ["tasks", "Show work list", "slash", "command"], }, { id: "clear", - label: "/clear — Clear screen", - keywords: ["clear", "slash", "command"], + label: "/clear", + keywords: ["clear", "Clear screen", "slash", "command"], }, ]) }) @@ -36,7 +36,7 @@ describe("filterPaletteCommands", () => { expect(filterPaletteCommands("", catalog)).toEqual(catalog) }) - test("matches by id, label, or keyword substring", () => { + test("matches by id, label, or description keyword substring", () => { expect(filterPaletteCommands("compact", catalog).map((c) => c.id)).toEqual([ "compact", ]) @@ -55,6 +55,6 @@ describe("paletteLabels", () => { const catalog = commandItemsFromRegistry([ { name: "tasks", description: "Show work list" }, ]) - expect(paletteLabels(catalog)).toEqual(["/tasks — Show work list"]) + expect(paletteLabels(catalog)).toEqual(["/tasks"]) }) }) diff --git a/src/tui-opentui/command-catalog.ts b/src/tui-opentui/command-catalog.ts index 484abc761..99d27a914 100644 --- a/src/tui-opentui/command-catalog.ts +++ b/src/tui-opentui/command-catalog.ts @@ -29,8 +29,10 @@ export function commandItemsFromRegistry( ): PaletteCommand[] { return commands.map((c) => ({ id: c.name, - label: `/${c.name} — ${c.description}`, - keywords: [c.name, "slash", "command"], + // Name-only rows keep the slash popup scannable; description stays in + // keywords so typed filter still finds prose matches. + label: `/${c.name}`, + keywords: [c.name, c.description, "slash", "command"], })) } diff --git a/src/tui-opentui/palette-paint.test.ts b/src/tui-opentui/palette-paint.test.ts index 662169c84..fdee19ffc 100644 --- a/src/tui-opentui/palette-paint.test.ts +++ b/src/tui-opentui/palette-paint.test.ts @@ -17,9 +17,9 @@ import { } from "./shell" const CATALOG: readonly PaletteCommand[] = [ - { id: "help", label: "/help — show keymap help", keywords: ["help"] }, - { id: "model", label: "/model — switch model / provider", keywords: ["model"] }, - { id: "mcp", label: "/mcp — manage MCP servers", keywords: ["mcp"] }, + { id: "help", label: "/help", keywords: ["help", "show keymap help"] }, + { id: "model", label: "/model", keywords: ["model", "switch model / provider"] }, + { id: "mcp", label: "/mcp", keywords: ["mcp", "manage MCP servers"] }, ] async function paletteFrame(width: number): Promise { @@ -46,15 +46,36 @@ function rowFor(rows: readonly string[], label: string): string | undefined { } describe("command list rows", () => { - test("shows the filter prompt with no title rule above it", async () => { + test("slash mode omits the orphan filter row and the title rule", async () => { const rows = await paletteFrame(100) expect(rows.some((r) => r.startsWith("─ command palette ─"))).toBe(false) + // Default open is typeToFilter:false — query lives in the prompt. + expect(rows.some((r) => r.trim() === ">")).toBe(false) + }) + + test("typed filter mode keeps the Amp-style filter prompt", async () => { + const rows = await withTestRenderer( + async (h) => { + const shell = createAppShell(h.renderer, { + terminal: { columns: 100, rows: 32 }, + wireKeys: false, + run: "idle", + }) + openPalette(shell, { catalog: CATALOG, typeToFilter: true }) + await h.renderOnce() + return h + .captureCharFrame() + .split("\n") + .map((line) => line.replace(/^\s*│/, "").replace(/│\s*$/, "").trimEnd()) + }, + { width: 100, height: 32 }, + ) expect(rows.some((r) => r.trim() === ">")).toBe(true) }) test("has no leading selection marker or kind column", async () => { const rows = await paletteFrame(100) - const help = rowFor(rows, "show keymap help") + const help = rowFor(rows, "/help") expect(help).toBeDefined() expect(help).not.toContain(">") expect(help).not.toContain("view") diff --git a/src/tui-opentui/prompt-slash-exit.test.ts b/src/tui-opentui/prompt-slash-exit.test.ts index 061561916..839e1e518 100644 --- a/src/tui-opentui/prompt-slash-exit.test.ts +++ b/src/tui-opentui/prompt-slash-exit.test.ts @@ -19,9 +19,9 @@ import { } from "./shell" const CATALOG: readonly PaletteCommand[] = [ - { id: "model", label: "/model — switch model" }, - { id: "mcp", label: "/mcp — manage MCP servers" }, - { id: "compact", label: "/compact — compact history" }, + { id: "model", label: "/model" }, + { id: "mcp", label: "/mcp" }, + { id: "compact", label: "/compact" }, ] type Ctx = { diff --git a/src/tui-opentui/shell.ts b/src/tui-opentui/shell.ts index daf267b60..2a7ca677f 100644 --- a/src/tui-opentui/shell.ts +++ b/src/tui-opentui/shell.ts @@ -3541,12 +3541,13 @@ function repaintPalette(shell: AppShell): void { kind: "palette", title: state.title, items: labels, - // Filter query row, Amp-style: the palette always shows what it filtered on. - body: `> ${state.query}`, + // Typed filter row only when the overlay owns keystrokes. The `/` popup + // keeps its query in the prompt, so a body of `>` would be orphan chrome. + ...(state.typeToFilter ? { body: `> ${state.query}` } : {}), frameId: "command-palette", }) - // No title rule row: the box is only ever the palette, and the filter row - // underneath already shows what's typed — a second header said nothing new. + // No title rule row: the box is only ever the palette, and when a filter + // row is present it already shows what's typed. shell.overlayTitle.visible = false shell.overlayTitle.content = "" paintOverlayList(shell) diff --git a/src/tui-opentui/wave6.test.ts b/src/tui-opentui/wave6.test.ts index 29829616b..950e28361 100644 --- a/src/tui-opentui/wave6.test.ts +++ b/src/tui-opentui/wave6.test.ts @@ -29,9 +29,9 @@ import { stringWidth } from "../tui/view/height" import type { PaletteCommand } from "./command-catalog" const CATALOG: readonly PaletteCommand[] = [ - { id: "compact", label: "/compact — compact history" }, - { id: "help", label: "/help — show keymap help" }, - { id: "model", label: "/model — switch model" }, + { id: "compact", label: "/compact" }, + { id: "help", label: "/help" }, + { id: "model", label: "/model" }, ] describe("Wave 6: command list", () => { @@ -56,9 +56,9 @@ describe("Wave 6: command list", () => { await h.renderOnce() const frame = h.captureCharFrame() - // The list drops its title rule row, so identify it on screen by - // its filter prompt and first row rather than the word "palette". - expect(frame).toMatch(/│\s*>\s*│/) + // Slash mode has no title rule and no orphan filter row — identify + // the list by its name-only command labels. + expect(frame).not.toMatch(/│\s*>\s*│/) expect(frame).toContain("/compact") // List labels live in overlayItems (frame may clip first row under tight height). expect(shell.overlayItems[0]).toBe(CATALOG[0]!.label)