diff --git a/packages/views/editor/extensions/slash-command-suggestion.test.tsx b/packages/views/editor/extensions/slash-command-suggestion.test.tsx
index a2b6799ba..66f94eb60 100644
--- a/packages/views/editor/extensions/slash-command-suggestion.test.tsx
+++ b/packages/views/editor/extensions/slash-command-suggestion.test.tsx
@@ -346,18 +346,39 @@ describe("buildBuiltinCommandItems", () => {
expect(buildBuiltinCommandItems("")).toEqual(BUILTIN_COMMANDS);
});
- it("includes /note while the query is a prefix of it", () => {
+ it("includes /note while the query is a prefix of the label", () => {
expect(buildBuiltinCommandItems("no").map((c) => c.id)).toEqual(["note"]);
expect(buildBuiltinCommandItems("NOTE").map((c) => c.id)).toEqual(["note"]);
});
- it("matches against the description as well as the label", () => {
- expect(buildBuiltinCommandItems("agent").map((c) => c.id)).toEqual([
- "note",
- ]);
+ it("matches the label as a prefix only — not the description", () => {
+ // "agent" appears in the description but is not a label prefix.
+ expect(buildBuiltinCommandItems("agent")).toEqual([]);
+ // A non-prefix substring of the label does not match either.
+ expect(buildBuiltinCommandItems("ote")).toEqual([]);
});
it("returns nothing for a query that matches no command", () => {
expect(buildBuiltinCommandItems("deploy")).toEqual([]);
});
});
+
+describe("SlashCommandList built-in command rendering", () => {
+ it("renders the localized description for a built-in command", () => {
+ const { getByText } = render(
+
+
+ ,
+ );
+
+ expect(getByText("/note")).toBeInTheDocument();
+ expect(
+ getByText("Add a note — won't trigger the assigned agent"),
+ ).toBeInTheDocument();
+ });
+});
diff --git a/packages/views/editor/extensions/slash-command-suggestion.tsx b/packages/views/editor/extensions/slash-command-suggestion.tsx
index 2e1acdb90..8eda201b4 100644
--- a/packages/views/editor/extensions/slash-command-suggestion.tsx
+++ b/packages/views/editor/extensions/slash-command-suggestion.tsx
@@ -23,10 +23,20 @@ import { createSuggestionPopupRender } from "./suggestion-popup";
const MAX_ITEMS = 20;
+/** Known built-in command ids — the keys under editor `slash_command.commands`. */
+export type BuiltinCommandKey = "note";
+
export interface SlashCommandItem {
id: string;
label: string;
- description: string;
+ /** Raw description (skill picker). Built-in commands use descriptionKey. */
+ description?: string;
+ /**
+ * For built-in commands: the i18n key under editor `slash_command.commands`.
+ * When set, the menu renders the translated copy instead of `description`,
+ * so the visible string stays localized (the typed `/label` does not).
+ */
+ descriptionKey?: BuiltinCommandKey;
}
interface SlashCommandListProps {
@@ -107,27 +117,37 @@ export const SlashCommandList = forwardRef<
);
}
+ // Built-in commands carry an i18n key so the visible description stays
+ // localized; skills carry a raw description string from their config.
+ const describe = (item: SlashCommandItem): string | undefined =>
+ item.descriptionKey === "note"
+ ? t(($) => $.slash_command.commands.note)
+ : item.description;
+
return (
- {items.map((item, index) => (
-
- ))}
+ {items.map((item, index) => {
+ const description = describe(item);
+ return (
+
+ );
+ })}
);
});
@@ -227,21 +247,15 @@ export function createSlashCommandSuggestion(qc: QueryClient): Omit<
* `noteCommentPrefix` in server/internal/handler/comment.go.
*/
export const BUILTIN_COMMANDS: SlashCommandItem[] = [
- {
- id: "note",
- label: "note",
- description: "Add a note — won't trigger the assigned agent",
- },
+ { id: "note", label: "note", descriptionKey: "note" },
];
+// Match on the command label as a prefix only — the description is for display,
+// not search. With a single command this keeps the menu predictable (typing
+// `/no` surfaces `note`; an unrelated `/deploy` shows nothing).
export function buildBuiltinCommandItems(query: string): SlashCommandItem[] {
const q = query.toLowerCase();
- return BUILTIN_COMMANDS.filter(
- (c) =>
- !q ||
- c.label.toLowerCase().includes(q) ||
- c.description.toLowerCase().includes(q),
- );
+ return BUILTIN_COMMANDS.filter((c) => c.label.toLowerCase().startsWith(q));
}
export function createBuiltinCommandSuggestion(): Omit<
diff --git a/packages/views/locales/en/editor.json b/packages/views/locales/en/editor.json
index 35c06c015..7b4af4376 100644
--- a/packages/views/locales/en/editor.json
+++ b/packages/views/locales/en/editor.json
@@ -64,7 +64,10 @@
},
"slash_command": {
"no_skills_configured": "No skills configured",
- "no_results": "No matching skills"
+ "no_results": "No matching skills",
+ "commands": {
+ "note": "Add a note — won't trigger the assigned agent"
+ }
},
"code_block": {
"copy_code": "Copy code",
diff --git a/packages/views/locales/ja/editor.json b/packages/views/locales/ja/editor.json
index cc5cd3ca9..09a787914 100644
--- a/packages/views/locales/ja/editor.json
+++ b/packages/views/locales/ja/editor.json
@@ -80,6 +80,9 @@
},
"slash_command": {
"no_skills_configured": "設定済みスキルなし",
- "no_results": "一致するスキルなし"
+ "no_results": "一致するスキルなし",
+ "commands": {
+ "note": "メモを追加 — 担当エージェントをトリガーしません"
+ }
}
}
diff --git a/packages/views/locales/ko/editor.json b/packages/views/locales/ko/editor.json
index 115d5fb2d..63d4fd225 100644
--- a/packages/views/locales/ko/editor.json
+++ b/packages/views/locales/ko/editor.json
@@ -80,6 +80,9 @@
},
"slash_command": {
"no_skills_configured": "구성된 스킬 없음",
- "no_results": "일치하는 스킬 없음"
+ "no_results": "일치하는 스킬 없음",
+ "commands": {
+ "note": "메모 추가 — 담당 에이전트를 트리거하지 않음"
+ }
}
}
diff --git a/packages/views/locales/zh-Hans/editor.json b/packages/views/locales/zh-Hans/editor.json
index f9714c20a..c30ff2410 100644
--- a/packages/views/locales/zh-Hans/editor.json
+++ b/packages/views/locales/zh-Hans/editor.json
@@ -64,7 +64,10 @@
},
"slash_command": {
"no_skills_configured": "暂无配置的技能",
- "no_results": "没有匹配的技能"
+ "no_results": "没有匹配的技能",
+ "commands": {
+ "note": "添加备注 — 不会触发已分配的 Agent"
+ }
},
"code_block": {
"copy_code": "复制代码",