diff --git a/webui/src/components/thread/ThreadComposer.tsx b/webui/src/components/thread/ThreadComposer.tsx index 4fb0f4ab..23757353 100644 --- a/webui/src/components/thread/ThreadComposer.tsx +++ b/webui/src/components/thread/ThreadComposer.tsx @@ -286,6 +286,7 @@ interface SlashPaletteCommand { title: string; description: string; icon: string; + kind?: "skill"; argHint?: string; detail: string; badge?: string; @@ -1009,6 +1010,7 @@ export function ThreadComposer({ description, detail: description, icon: "brain", + kind: "skill" as const, recent: recentSlashCommands.includes(command), }; }) @@ -2582,6 +2584,7 @@ function SlashCommandPalette({ {commands.map((command, index) => { const Icon = COMMAND_ICONS[command.icon] ?? CircleHelp; const selected = index === selectedIndex; + const isSkill = command.kind === "skill"; const commandKey = slashCommandI18nKey(command.command); const title = t(`thread.composer.slash.commands.${commandKey}.title`, { defaultValue: command.title, @@ -2617,23 +2620,34 @@ function SlashCommandPalette({ - + {title} {command.detail || description} - - {command.badge || command.recent ? ( - - {command.badge ?? t("thread.composer.slash.badges.recent")} - - ) : null} - - {command.argHint ? `${command.command} ${command.argHint}` : command.command} + {!isSkill || command.badge || command.recent ? ( + + {command.badge || command.recent ? ( + + {command.badge ?? t("thread.composer.slash.badges.recent")} + + ) : null} + {!isSkill ? ( + + {command.argHint ? `${command.command} ${command.argHint}` : command.command} + + ) : null} - + ) : null} ); })} diff --git a/webui/src/tests/thread-composer.test.tsx b/webui/src/tests/thread-composer.test.tsx index d3727a4b..1fa83010 100644 --- a/webui/src/tests/thread-composer.test.tsx +++ b/webui/src/tests/thread-composer.test.tsx @@ -1146,14 +1146,15 @@ describe("ThreadComposer", () => { }); }); - it("opens skills only from a $ reference anywhere", () => { + it("opens skills only from a $ reference and prioritizes the skill name", () => { + const skillName = "arxiv-intelligence-filter"; render( { fireEvent.change(input, { target: { value: "/git", selectionStart: 4 } }); expect(screen.queryByRole("listbox", { name: "Slash commands" })).not.toBeInTheDocument(); - fireEvent.change(input, { target: { value: "please use $git", selectionStart: 15 } }); + fireEvent.change(input, { target: { value: "please use $arxiv", selectionStart: 17 } }); const palette = screen.getByRole("listbox", { name: "Slash commands" }); - expect(within(palette).getByRole("option", { name: /github/i })).toHaveTextContent("$github"); + const option = within(palette).getByRole("option", { name: new RegExp(skillName, "i") }); + const name = within(option).getByText(skillName); + expect(name).not.toHaveClass("truncate"); + expect(within(option).queryByText(`$${skillName}`)).not.toBeInTheDocument(); expect(within(palette).queryByText("/model")).not.toBeInTheDocument(); fireEvent.keyDown(input, { key: "Tab" }); - expect(input).toHaveValue("please use $github "); + expect(input).toHaveValue(`please use $${skillName} `); }); it("shows right-side source badges so users can distinguish CLI apps from MCP servers", () => {