authorgravatar for 92423096+nateEc@users.noreply.github.comYukun Shan <92423096+nateEc@users.noreply.github.com> 2026-09-01 01:28:31+08:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-08-31 10:28:31-07:00
logbba79cc254b65969bde6b6bfc3032c3b5b9316ae
treeb33c3fe8ea3fcbd32f56c31649199252de5c391c
parent2921050c698fb195e2f5590b7cfab8fdfe0ec729
signature Signed by PGP key B5690EEEBB952194

fix(web): hide invalid slash skill completions (#8904)


3 files changed, 56 insertions(+), 7 deletions(-)

apps/web/src/components/chat/ChatComposer.tsx+8-6
......@@ -146,7 +146,10 @@ import { ComposerPendingUserInputPanel } from "./ComposerPendingUserInputPanel";
146146import { ComposerPlanFollowUpBanner } from "./ComposerPlanFollowUpBanner";
147147import { ComposerControl, ComposerControlIcon, ComposerSelectControl } from "./ComposerControl";
148148import { resolveComposerMenuActiveItemId } from "./composerMenuHighlight";
149import { searchSlashCommandItems } from "./composerSlashCommandSearch";
149import {
150 searchSlashCommandItems,
151 slashCommandItemsForPromptPosition,
152} from "./composerSlashCommandSearch";
150153import {
151154 getComposerPromptInjectionState,
152155 getComposerProviderState,
......@@ -1360,11 +1363,10 @@ export const ChatComposer = memo(function ChatComposer(props: ChatComposerProps)
13601363 skill.description ??
13611364 (skill.scope ? `${skill.scope} skill` : ""),
13621365 }));
1363 const slashCommandItems = [
1364 ...builtInSlashCommandItems,
1365 ...providerSlashCommandItems,
1366 ...skillItems,
1367 ];
1366 const slashCommandItems = slashCommandItemsForPromptPosition(
1367 [...builtInSlashCommandItems, ...providerSlashCommandItems, ...skillItems],
1368 composerTrigger.rangeStart === 0,
1369 );
13681370 return searchSlashCommandItems(slashCommandItems, query);
13691371 }
13701372 if (composerTrigger.kind === "skill") {
apps/web/src/components/chat/composerSlashCommandSearch.test.ts+38-1
......@@ -2,7 +2,10 @@ import { describe, expect, it } from "vite-plus/test";
22import { ProviderDriverKind } from "@t3tools/contracts";
33
44import type { ComposerCommandItem } from "./ComposerCommandMenu";
5import { searchSlashCommandItems } from "./composerSlashCommandSearch";
5import {
6 searchSlashCommandItems,
7 slashCommandItemsForPromptPosition,
8} from "./composerSlashCommandSearch";
69
710describe("searchSlashCommandItems", () => {
811 const claudeDriver = ProviderDriverKind.make("claudeAgent");
......@@ -173,4 +176,38 @@ describe("searchSlashCommandItems", () => {
173176 "skill:claudeAgent:unslop",
174177 ]);
175178 });
179
180 it("hides skills from slash completion after the first message line", () => {
181 const items = [
182 {
183 id: "slash:model",
184 type: "slash-command",
185 command: "model",
186 label: "/model",
187 description: "Switch model",
188 },
189 {
190 id: "skill:claudeAgent:unslop",
191 type: "skill",
192 provider: claudeDriver,
193 skill: {
194 name: "unslop",
195 path: "/skills/unslop/SKILL.md",
196 enabled: true,
197 },
198 label: "/skill:unslop",
199 description: "Cut AI tells from writing",
200 },
201 ] satisfies Array<
202 Extract<ComposerCommandItem, { type: "slash-command" | "provider-slash-command" | "skill" }>
203 >;
204
205 expect(slashCommandItemsForPromptPosition(items, false).map((item) => item.id)).toEqual([
206 "slash:model",
207 ]);
208 expect(slashCommandItemsForPromptPosition(items, true).map((item) => item.id)).toEqual([
209 "slash:model",
210 "skill:claudeAgent:unslop",
211 ]);
212 });
176213});
apps/web/src/components/chat/composerSlashCommandSearch.ts+10
......@@ -12,6 +12,16 @@ type SlashSearchItem = Extract<
1212 { type: "slash-command" | "provider-slash-command" | "skill" }
1313>;
1414
15export function slashCommandItemsForPromptPosition(
16 items: ReadonlyArray<SlashSearchItem>,
17 isAtPromptStart: boolean,
18): SlashSearchItem[] {
19 if (isAtPromptStart) {
20 return [...items];
21 }
22 return items.filter((item) => item.type !== "skill");
23}
24
1525function scoreSlashCommandItem(item: SlashSearchItem, query: string): number | null {
1626 if (item.type === "skill") {
1727 if (query === "skill") {