fix(command-palette): match file results against the full relative path

Cmd+P scored server file hits by basename only, so a query like
"solo-is-a" returned nothing for solo-is-a-team-size/index.md.
Score by relativePath and widen the server candidate limit to 40
so client-side reranking has enough to work with.
This commit is contained in:
Bohdan Triapitsyn
2026-08-24 14:19:25 +03:00
parent 97edd033f9
commit 279fa8b691
3 changed files with 28 additions and 3 deletions
@@ -352,7 +352,7 @@ export const CommandPalette: React.FC = () => {
return; return;
} }
let cancelled = false; let cancelled = false;
void searchFiles(currentRoot, trimmedQuery, 10, { type: 'file' }) void searchFiles(currentRoot, trimmedQuery, 40, { type: 'file' })
.then((results) => { .then((results) => {
if (cancelled) return; if (cancelled) return;
setFileResults( setFileResults(
@@ -16,4 +16,27 @@ describe('commandPaletteFilesState', () => {
expect(scoreCommandPaletteFiles(fileResults, 'alpha', freshKey, staleKey)).toEqual([]); expect(scoreCommandPaletteFiles(fileResults, 'alpha', freshKey, staleKey)).toEqual([]);
expect(scoreCommandPaletteFiles(fileResults, 'alpha', freshKey, freshKey)).toHaveLength(1); expect(scoreCommandPaletteFiles(fileResults, 'alpha', freshKey, freshKey)).toHaveLength(1);
}); });
test('matches directory segments of the relative path, not just the basename', () => {
const fileResults = [
{ name: 'index.md', path: '/kb/solo-is-a-team-size/index.md', relativePath: 'solo-is-a-team-size/index.md' },
{ name: 'index.md', path: '/kb/software-developer/index.md', relativePath: 'software-developer/index.md' },
];
const key = buildCommandPaletteFileSearchKey('/kb', 'solo-is-a');
const scored = scoreCommandPaletteFiles(fileResults, 'solo-is-a', key, key);
expect(scored).toHaveLength(1);
expect(scored[0].item.relativePath).toBe('solo-is-a-team-size/index.md');
});
test('ranks prefix path matches above later substring matches', () => {
const fileResults = [
{ name: 'index.md', path: '/kb/notes/solo/index.md', relativePath: 'notes/solo/index.md' },
{ name: 'index.md', path: '/kb/solo-is-a-team-size/index.md', relativePath: 'solo-is-a-team-size/index.md' },
];
const key = buildCommandPaletteFileSearchKey('/kb', 'solo');
const scored = scoreCommandPaletteFiles(fileResults, 'solo', key, key);
expect(scored[0].item.relativePath).toBe('solo-is-a-team-size/index.md');
});
}); });
@@ -11,7 +11,7 @@ export const buildCommandPaletteFileSearchKey = (
return JSON.stringify([currentRoot, trimmedQuery]); return JSON.stringify([currentRoot, trimmedQuery]);
}; };
export const scoreCommandPaletteFiles = <T extends { name: string }>( export const scoreCommandPaletteFiles = <T extends { name: string; relativePath: string }>(
fileResults: T[], fileResults: T[],
trimmedQuery: string, trimmedQuery: string,
fileSearchKey: string, fileSearchKey: string,
@@ -21,7 +21,9 @@ export const scoreCommandPaletteFiles = <T extends { name: string }>(
return []; return [];
} }
return scoreByFuzzyQuery(fileResults, trimmedQuery, (file) => file.name, { // Score against the full relative path: queries like "solo-is-a" must match
// solo-is-a-team-size/index.md even though the basename is just index.md.
return scoreByFuzzyQuery(fileResults, trimmedQuery, (file) => file.relativePath || file.name, {
limit: 10, limit: 10,
threshold: 0.4, threshold: 0.4,
}); });