authorgravatar for 62337003+Bil0000@users.noreply.github.comBilal Bakr <62337003+Bil0000@users.noreply.github.com> 2026-10-13 23:34:37+03:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-10-13 15:34:37-05:00
loga01c48f86245f2a779f9af84042f3b09fbdeb91f
treee47997cc46567f2e4ac1496f0777c8df4ea28481
parent6947f0d9d85dd92e74b13a70bd3d97a27d899506
signature Signed by PGP key B5690EEEBB952194

fix(composer): allow slash skill search within messages (#19107)


4 files changed, 66 insertions(+), 16 deletions(-)

apps/web/src/composer-logic.test.ts+25
......@@ -246,6 +246,31 @@ describe("composerSubmissionIntentForKey", () => {
246246});
247247
248248describe("detectComposerTrigger", () => {
249 it.each(["Use ", "Use\t", "Use\n "])("detects inline slash skills after %j", (prefix) => {
250 const text = `${prefix}/skill:review later`;
251 const cursor = `${prefix}/skill:review`.length;
252 expect(detectComposerTrigger(text, cursor)).toEqual({
253 kind: "slash-command",
254 query: "skill:review",
255 rangeStart: prefix.length,
256 rangeEnd: cursor,
257 });
258 });
259
260 it.each(["Use /", "Use /review"])("detects the inline slash token in %j", (text) => {
261 expect(detectComposerTrigger(text, text.length)).toEqual({
262 kind: "slash-command",
263 query: text.slice(5),
264 rangeStart: 4,
265 rangeEnd: text.length,
266 });
267 });
268
269 it.each(["Use/review", "https://example.com/review", "Use /review "])(
270 "does not trigger for %j",
271 (text) => expect(detectComposerTrigger(text, text.length)).toBeNull(),
272 );
273
249274 it("detects @path trigger at cursor", () => {
250275 const text = "Please check @src/com";
251276 const trigger = detectComposerTrigger(text, text.length);
apps/web/src/composer-logic.ts+8-16
......@@ -258,24 +258,16 @@ export function isCollapsedCursorAdjacentToInlineToken(
258258
259259export function detectComposerTrigger(text: string, cursorInput: number): ComposerTrigger | null {
260260 const cursor = clampCursor(text, cursorInput);
261 const lineStart = text.lastIndexOf("\n", Math.max(0, cursor - 1)) + 1;
262 const linePrefix = text.slice(lineStart, cursor);
263
264 if (linePrefix.startsWith("/")) {
265 const commandMatch = /^\/(\S*)$/.exec(linePrefix);
266 if (commandMatch) {
267 const commandQuery = commandMatch[1] ?? "";
268 return {
269 kind: "slash-command",
270 query: commandQuery,
271 rangeStart: lineStart,
272 rangeEnd: cursor,
273 };
274 }
275 }
276
277261 const tokenStart = tokenStartForCursor(text, cursor);
278262 const token = text.slice(tokenStart, cursor);
263 if (token.startsWith("/")) {
264 return {
265 kind: "slash-command",
266 query: token.slice(1),
267 rangeStart: tokenStart,
268 rangeEnd: cursor,
269 };
270 }
279271 const pullRequestMatch = /^#([\p{L}\p{N}][\p{L}\p{N}_-]*)?$/u.exec(token);
280272 if (pullRequestMatch) {
281273 return {
packages/shared/src/composerTrigger.test.ts+25
......@@ -3,6 +3,31 @@ import { describe, expect, it } from "vite-plus/test";
33import { detectComposerTrigger, serializeComposerFileLink } from "./composerTrigger.ts";
44
55describe("detectComposerTrigger", () => {
6 it.each(["Use ", "Use\t", "Use\n "])("detects inline slash skills after %j", (prefix) => {
7 const text = `${prefix}/skill:review later`;
8 const cursor = `${prefix}/skill:review`.length;
9 expect(detectComposerTrigger(text, cursor)).toEqual({
10 kind: "slash-command",
11 query: "skill:review",
12 rangeStart: prefix.length,
13 rangeEnd: cursor,
14 });
15 });
16
17 it.each(["Use /", "Use /review"])("detects the inline slash token in %j", (text) => {
18 expect(detectComposerTrigger(text, text.length)).toEqual({
19 kind: "slash-command",
20 query: text.slice(5),
21 rangeStart: 4,
22 rangeEnd: text.length,
23 });
24 });
25
26 it.each(["Use/review", "https://example.com/review", "Use /review "])(
27 "does not trigger for %j",
28 (text) => expect(detectComposerTrigger(text, text.length)).toBeNull(),
29 );
30
631 it.each(["$", "€", "£", "¥", "₹", "₩", "₿", "𑿝"])(
732 "detects %s skill prefixes and their source range",
833 (prefix) => {
packages/shared/src/composerTrigger.ts+8
......@@ -100,6 +100,14 @@ export function detectComposerTrigger(
100100 const tokenStart = tokenIdx + 1;
101101
102102 const token = text.slice(tokenStart, cursor);
103 if (token.startsWith("/")) {
104 return {
105 kind: "slash-command",
106 query: token.slice(1),
107 rangeStart: tokenStart,
108 rangeEnd: cursor,
109 };
110 }
103111 const pullRequestMatch = /^#([\p{L}\p{N}][\p{L}\p{N}_-]*)?$/u.exec(token);
104112 if (pullRequestMatch)
105113 return {