fix(ui): rank a search hit by its name in the index too

Since the branch node reads "cbm · working tree", a search for "working"
ranked it behind the folders of the other hits and cut it from the list.

Signed-off-by: Bernhard Jackiewicz <bj@techport.io>
This commit is contained in:
Bernhard Jackiewicz committed 2026-10-04 19:19:51 +02:00
1 parent ff3e88b238
commit 0f9340d883
2 files changed
+31 -2

No files matched your search

@@ -0,0 +1,24 @@
import { describe, expect, it } from 'vitest';
import { choicesFromSearchHits, searchPriority } from './GalaxyNavigator';
/*
* Handtest 04.10. (abends): In cbm fand "working" den Branch-Knoten nicht mehr.
* Seit er "cbm · working tree" heisst, beginnt sein gezeigter Name nicht mit dem
* Suchwort, und die Ordner der anderen Treffer schoben ihn aus den zwoelf Plaetzen.
* Sein Name im Index ("working-tree") zaehlt wie der gezeigte.
*/
describe('search priority', () => {
const [branch] = choicesFromSearchHits([{ qualified_name: 'cbm.__branch__.working-tree', name: 'working-tree', label: 'Branch', file_path: '{}' } as never]);
it('ranks a hit whose name in the index starts with the search word with the name matches', () => {
expect(branch.name).toBe('cbm · working tree');
expect(searchPriority(branch, 'working')).toBe(1);
expect(searchPriority(branch, 'working-tree')).toBe(0);
});
it('keeps folders behind name matches and other hits behind folders', () => {
const folder = { key: 'docs/', name: 'docs/', detail: 'Indexed folder', kind: 'Folder', scope: { kind: 'folder' as const, path: 'docs', name: 'docs/' } };
expect(searchPriority(folder, 'working')).toBe(2);
expect(searchPriority({ ...branch, name: 'other', scope: { kind: 'symbol' as const, qualifiedName: 'a.b', name: 'other' } }, 'working')).toBe(3);
});
});
+7 -2
View File
@@ -32,6 +32,12 @@ export function choicesFromSearchHits(hits: readonly SearchGraphHit[]): Choice[]
}
return [...choices.values()];
}
/** Exact names first, then names that start with the search word, then folders and files, then the rest.
* The name in the index counts like the shown one: "cbm · working tree" is "working-tree" there. */
export function searchPriority(choice: Choice, needle: string): number {
const names = [choice.name, 'name' in choice.scope ? choice.scope.name : undefined].filter((name): name is string => Boolean(name)).map(name => name.toLocaleLowerCase());
return names.includes(needle) ? 0 : names.some(name => name.startsWith(needle)) ? 1 : choice.kind === 'Folder' || choice.kind === 'File' ? 2 : 3;
}
/** One search for graph symbols and file/folder clusters, including unloaded symbols. */
export default function GalaxyNavigator({ nodes, onSelect, onSelectScope, project, fetch: fetchImpl, embedded = false }: {
nodes: readonly GraphNode[]; onSelect: (node: GraphNode) => void; onSelectScope?: (scope: GraphScope) => void;
@@ -97,8 +103,7 @@ export default function GalaxyNavigator({ nodes, onSelect, onSelectScope, projec
choices.push({ key: path, name: path, detail: `${count.toLocaleString()} loaded nodes`, kind: folder ? 'Folder' : 'File',
scope: { kind: folder ? 'folder' : 'file', path: path.replace(/\/$/, ''), name: path } });
}
const priority = (choice: Choice) => choice.name.toLocaleLowerCase() === needle ? 0
: choice.name.toLocaleLowerCase().startsWith(needle) ? 1 : choice.kind === 'Folder' || choice.kind === 'File' ? 2 : 3;
const priority = (choice: Choice) => searchPriority(choice, needle);
const unique = new Map(choices.map(choice => [choice.key, choice]));
for (const choice of remote) if (!unique.has(choice.key)) unique.set(choice.key, choice);
return [...unique.values()].sort((a, b) => priority(a) - priority(b) || a.name.localeCompare(b.name)).slice(0, 12);