diff --git a/src/renderer/src/components/native-chat/native-chat-composer-state.test.ts b/src/renderer/src/components/native-chat/native-chat-composer-state.test.ts index 68998093624..713f36d2957 100644 --- a/src/renderer/src/components/native-chat/native-chat-composer-state.test.ts +++ b/src/renderer/src/components/native-chat/native-chat-composer-state.test.ts @@ -326,18 +326,29 @@ describe('native skill and command picker', () => { expect(names).toContain('home-skill-2') }) - it('keeps ranked order when a query narrows a catalog past the cap', () => { - const skills = Array.from({ length: 600 }, (_, index) => - skill({ - name: `deploy-${String(index).padStart(4, '0')}`, - sourceKind: 'repo', - skillFilePath: `/repo/.agents/skills/deploy-${index}/SKILL.md` - }) - ) - const names = buildNativeChatPickerItems([], skills, 'deploy-0001', '/').map( - (item) => item.name - ) - expect(names[0]).toBe('deploy-0001') + it('keeps a queried catalog in rank order rather than spreading it across scopes', () => { + // 600 prefix matches (rank 1) in repo vs 10 substring matches (rank 2) in home: + // rank order fills the cap with repo rows, a scope budget would interleave home rows. + const skills = [ + ...Array.from({ length: 600 }, (_, index) => + skill({ + name: `deploy-${String(index).padStart(4, '0')}`, + sourceKind: 'repo', + skillFilePath: `/repo/.agents/skills/deploy-${index}/SKILL.md` + }) + ), + ...Array.from({ length: 10 }, (_, index) => + skill({ + name: `alpha-deploy-${index}`, + sourceKind: 'home', + skillFilePath: `/home/.claude/skills/alpha-deploy-${index}/SKILL.md` + }) + ) + ] + const names = buildNativeChatPickerItems([], skills, 'deploy', '/').map((item) => item.name) + expect(names).toHaveLength(500) + expect(names.every((name) => name.startsWith('deploy-'))).toBe(true) + expect(names).not.toContain('alpha-deploy-0') }) it('keeps a long token-safe name intact for insertion instead of truncating it', () => { diff --git a/src/renderer/src/components/native-chat/native-chat-picker-items.ts b/src/renderer/src/components/native-chat/native-chat-picker-items.ts index cf42d7e9f94..467d9013566 100644 --- a/src/renderer/src/components/native-chat/native-chat-picker-items.ts +++ b/src/renderer/src/components/native-chat/native-chat-picker-items.ts @@ -38,7 +38,9 @@ export type NativeChatSkillDiscoverySnapshot = { // Why: a safety valve against pathological catalogs, not a display policy. The // menu scrolls, so a low cap only hid skills — and because rows are scope-sorted, // one large scope silently erased every smaller one (#14690). -const PICKER_RESULT_LIMIT = 500 +const SKILL_RESULT_LIMIT = 500 +/** Commands are a small curated catalog; keep their original bound. */ +const COMMAND_RESULT_LIMIT = 50 const SCOPE_PRIORITY: Record = { repo: 0, home: 1, @@ -77,12 +79,12 @@ export function buildNativeChatPickerItems( query ) return [ - ...commandItems.slice(0, PICKER_RESULT_LIMIT), + ...commandItems.slice(0, COMMAND_RESULT_LIMIT), // Ranked results are ordered by relevance, so a plain slice keeps the best // matches; the unfiltered list is scope-ordered and needs a fair budget. ...(query - ? skillItems.slice(0, PICKER_RESULT_LIMIT) - : limitSkillsAcrossScopes(skillItems, PICKER_RESULT_LIMIT)) + ? skillItems.slice(0, SKILL_RESULT_LIMIT) + : limitSkillsAcrossScopes(skillItems, SKILL_RESULT_LIMIT)) ] }