From 5ce121e613440558a993efc9774d80619312eeb6 Mon Sep 17 00:00:00 2001 From: Yannic Charlon <52761674+JustYannicc@users.noreply.github.com> Date: Fri, 3 Jul 2026 23:35:02 +0200 Subject: [PATCH] perf(files): map launcher file result commands by path --- .../test-launcher-file-result-command-map.mjs | 215 ++++++++++++++++++ .../src/hooks/useLauncherCommandModel.ts | 103 ++------- .../src/utils/launcher-file-candidates.ts | 124 ++++++++++ 3 files changed, 355 insertions(+), 87 deletions(-) create mode 100644 scripts/test-launcher-file-result-command-map.mjs create mode 100644 src/renderer/src/utils/launcher-file-candidates.ts diff --git a/scripts/test-launcher-file-result-command-map.mjs b/scripts/test-launcher-file-result-command-map.mjs new file mode 100644 index 00000000..d627ecbf --- /dev/null +++ b/scripts/test-launcher-file-result-command-map.mjs @@ -0,0 +1,215 @@ +#!/usr/bin/env node + +import test from 'node:test'; +import assert from 'node:assert/strict'; +import path from 'node:path'; +import { performance } from 'node:perf_hooks'; +import { build } from 'esbuild'; + +const root = path.resolve('.'); +const { + buildLauncherFileCandidates, + buildLauncherFileResultCommandByPath, +} = await importBundledTs(path.join(root, 'src/renderer/src/utils/launcher-file-candidates.ts')); + +async function importBundledTs(absPath) { + const result = await build({ + absWorkingDir: root, + entryPoints: [absPath], + bundle: true, + write: false, + format: 'esm', + platform: 'node', + target: 'node20', + logLevel: 'silent', + loader: { + '.ts': 'ts', + '.tsx': 'tsx', + '.js': 'js', + '.jsx': 'jsx', + '.json': 'json', + }, + }); + const code = result.outputFiles[0].text; + const dataUrl = `data:text/javascript;base64,${Buffer.from(code).toString('base64')}`; + return import(dataUrl); +} + +function makeResult(index, overrides = {}) { + const parentPath = `/Users/test/Documents/project-${String(index % 30).padStart(2, '0')}`; + const name = `alpha-file-${String(index).padStart(4, '0')}.txt`; + const filePath = `${parentPath}/${name}`; + return { + path: filePath, + name, + parentPath, + displayPath: `~/Documents/project-${String(index % 30).padStart(2, '0')}`, + isDirectory: false, + mtimeMs: Date.now() - (index % 24) * 60 * 60 * 1000, + birthtimeMs: Date.now() - (index % 24) * 60 * 60 * 1000, + topLevelRoot: 'Documents', + homeRelativeDepth: 3, + depth: 3, + noisyPathSegmentCount: 0, + ...overrides, + }; +} + +function makeCommand(result, index, overrides = {}) { + return { + id: `system-file-result:${index}`, + title: result.name, + subtitle: result.displayPath, + keywords: [result.name, result.parentPath, result.displayPath], + category: 'system', + path: result.path, + ...overrides, + }; +} + +function candidateSignature(candidates) { + return candidates.map((candidate) => [ + candidate.stableKey, + candidate.label, + candidate.pathOrUrl, + candidate.matchKind, + candidate.matchScore, + candidate.subtype, + candidate.command.id, + candidate.command.title, + candidate.command.path, + candidate.finalScore, + ]); +} + +function checksumCandidates(candidates) { + let checksum = candidates.length; + for (const candidate of candidates) { + checksum += candidate.command.id.length; + checksum += candidate.stableKey.length; + checksum += Math.round(candidate.finalScore); + } + return checksum; +} + +function stats(values) { + const sorted = [...values].sort((a, b) => a - b); + return { + min: Number(sorted[0].toFixed(3)), + median: Number(sorted[Math.floor(sorted.length / 2)].toFixed(3)), + max: Number(sorted[sorted.length - 1].toFixed(3)), + }; +} + +function measure(iterations, fn) { + const times = []; + let checksum = 0; + for (let index = 0; index < iterations; index += 1) { + const started = performance.now(); + checksum += checksumCandidates(fn()); + times.push(performance.now() - started); + } + return { ...stats(times), checksum }; +} + +test('launcher file command map preserves first path match and result order', () => { + const duplicatePath = '/Users/test/Documents/alpha-duplicate.txt'; + const duplicateResult = makeResult(1, { + path: duplicatePath, + name: 'alpha-duplicate.txt', + parentPath: '/Users/test/Documents', + displayPath: '~/Documents', + }); + const missingCommandResult = makeResult(2, { + path: '/Users/test/Documents/alpha-missing.txt', + name: 'alpha-missing.txt', + parentPath: '/Users/test/Documents', + displayPath: '~/Documents', + }); + const tailResult = makeResult(3, { + path: '/Users/test/Documents/alpha-tail.txt', + name: 'alpha-tail.txt', + parentPath: '/Users/test/Documents', + displayPath: '~/Documents', + }); + + const firstDuplicateCommand = makeCommand(duplicateResult, 1, { title: 'First duplicate command' }); + const secondDuplicateCommand = makeCommand(duplicateResult, 2, { title: 'Second duplicate command' }); + const tailCommand = makeCommand(tailResult, 3, { title: 'Tail command' }); + const commandByPath = buildLauncherFileResultCommandByPath([ + firstDuplicateCommand, + secondDuplicateCommand, + tailCommand, + ]); + + assert.equal(commandByPath.get(duplicatePath), firstDuplicateCommand); + + const candidates = buildLauncherFileCandidates({ + launcherFileResults: [duplicateResult, missingCommandResult, tailResult], + fileResultCommandByPath: commandByPath, + searchQuery: 'alpha', + rootSearchRanking: {}, + }); + + assert.deepEqual( + candidates.map((candidate) => candidate.command.title), + ['First duplicate command', 'Tail command'] + ); + assert.deepEqual( + candidates.map((candidate) => candidate.pathOrUrl), + [duplicatePath, tailResult.path] + ); +}); + +test('launcher file candidate assembly handles 3000 results with path-map lookup', () => { + const count = 3000; + const iterations = 25; + const launcherFileResults = Array.from({ length: count }, (_, index) => makeResult(index)); + const fileResultCommands = launcherFileResults.map((result, index) => makeCommand(result, index)); + const fileResultCommandByPath = buildLauncherFileResultCommandByPath(fileResultCommands); + const findBackedLookup = { + get(filePath) { + return fileResultCommands.find((command) => command.path === filePath); + }, + }; + + const input = { + launcherFileResults, + searchQuery: 'alpha', + rootSearchRanking: {}, + }; + const mapCandidates = buildLauncherFileCandidates({ + ...input, + fileResultCommandByPath, + }); + const findCandidates = buildLauncherFileCandidates({ + ...input, + fileResultCommandByPath: findBackedLookup, + }); + + assert.equal(mapCandidates.length, count); + assert.deepEqual(candidateSignature(mapCandidates), candidateSignature(findCandidates)); + + const mapStats = measure(iterations, () => buildLauncherFileCandidates({ + ...input, + fileResultCommandByPath, + })); + const findStats = measure(iterations, () => buildLauncherFileCandidates({ + ...input, + fileResultCommandByPath: findBackedLookup, + })); + + assert.equal(mapStats.checksum, findStats.checksum); + assert.ok( + mapStats.median < findStats.median, + `expected path-map assembly median ${mapStats.median}ms to be below find-backed median ${findStats.median}ms` + ); + + console.log('Launcher file candidate assembly benchmark', JSON.stringify({ + count, + iterations, + mapStats, + findStats, + speedup: Number((findStats.median / mapStats.median).toFixed(2)), + })); +}); diff --git a/src/renderer/src/hooks/useLauncherCommandModel.ts b/src/renderer/src/hooks/useLauncherCommandModel.ts index 44ca0fbc..6ab6d0d8 100644 --- a/src/renderer/src/hooks/useLauncherCommandModel.ts +++ b/src/renderer/src/hooks/useLauncherCommandModel.ts @@ -43,7 +43,6 @@ import { rankRootSearchCandidates, scoreRootSearchCandidate, scoreRootSearchFields, - type MatchKind, type RootSearchCandidate, type RootSearchRankingState, type RootSearchSubtype, @@ -52,6 +51,11 @@ import { assembleRootSearchSections, isRootResultPromotionCandidate, } from '../utils/root-search-sections'; +import { + buildLauncherFileCandidates, + buildLauncherFileResultCommandByPath, + coerceRootSearchMatchKind as coerceMatchKind, +} from '../utils/launcher-file-candidates'; import type { LauncherCommandSection } from '../components/LauncherCommandList'; export type GroupedLauncherCommands = { @@ -138,53 +142,6 @@ function inferCommandSubtype(command: CommandInfo): RootSearchSubtype { return 'system-command'; } -function coerceMatchKind(value: string | undefined, fallback: MatchKind): MatchKind { - switch (value) { - case 'exact': - case 'alias-exact': - case 'nickname-exact': - case 'prefix': - case 'token-prefix': - case 'compact-prefix': - case 'word-boundary-fuzzy': - case 'contains': - case 'subsequence': - case 'description': - case 'path': - case 'url': - return value; - default: - return fallback; - } -} - -function getFileFreshnessBoost(result: IndexedFileSearchResult): number { - const touched = Math.max(Number(result.mtimeMs || 0), Number(result.birthtimeMs || 0)); - if (!touched) return 0; - const ageHours = Math.max(0, (Date.now() - touched) / (60 * 60 * 1000)); - const ageDays = ageHours / 24; - if (ageHours <= 24) return 120; - if (ageDays <= 7) return 90; - if (ageDays <= 30) return 45; - if (ageDays <= 90) return 15; - return 0; -} - -function getFileLocationBoost(result: IndexedFileSearchResult): number { - const topLevelRoot = String(result.topLevelRoot || '').trim(); - const depth = Number(result.homeRelativeDepth || result.depth || 0); - const protectedRoot = topLevelRoot === 'Desktop' || topLevelRoot === 'Documents' || topLevelRoot === 'Downloads'; - if (!protectedRoot) return 20; - return 120 - Math.min(90, Math.max(0, depth - 2) * 18); -} - -function getFileDepthPenalty(result: IndexedFileSearchResult): number { - const depth = Math.max(0, Number(result.homeRelativeDepth || result.depth || 0)); - if (depth <= 2) return 0; - if (depth <= 4) return (depth - 2) * 25; - return Math.min(260, 50 + (depth - 4) * 35); -} - type BrowserLauncherProfile = { id?: string; browserId?: BrowserSearchSource | string; @@ -391,6 +348,10 @@ export function useLauncherCommandModel({ }), [launcherFileResults, launcherFileIcons, homeDir] ); + const fileResultCommandByPath = useMemo( + () => buildLauncherFileResultCommandByPath(fileResultCommands), + [fileResultCommands] + ); const pinnedFileCommands = useMemo( () => @@ -565,45 +526,13 @@ export function useLauncherCommandModel({ const fileCandidates = useMemo(() => { if (!hasSearchQuery || aiMode || rootBangState.mode !== 'none') return []; - return launcherFileResults - .map((result) => { - const command = fileResultCommands.find((item) => item.path === result.path); - if (!command) return null; - const scored = scoreRootSearchFields(searchQuery, [ - { value: result.name, kind: 'label', weight: 1 }, - { value: result.parentPath, kind: 'path', weight: 0.72 }, - { value: result.displayPath, kind: 'path', weight: 0.72 }, - { value: result.path, kind: 'path', weight: 0.68 }, - ]); - if (!scored.matched) return null; - const subtype: RootSearchSubtype = result.isDirectory ? 'folder' : 'file'; - const matchKind = coerceMatchKind(result.matchKind, scored.matchKind); - const weakFolderMatch = subtype === 'folder' && (matchKind === 'contains' || matchKind === 'subsequence' || matchKind === 'path'); - const stableKey = `file:${normalizeRootSearchStableValue(result.path)}`; - return scoreRootSearchCandidate({ - command: { - ...command, - rootSearchStableKey: stableKey, - rootSearchSource: 'file', - rootSearchSubtype: subtype, - }, - source: 'file', - subtype, - stableKey, - label: result.name, - description: result.displayPath, - pathOrUrl: result.path, - matchKind, - matchScore: scored.matchScore, - sourceQualityBoost: subtype === 'file' ? 8 : weakFolderMatch ? -10 : 0, - freshnessBoost: getFileFreshnessBoost(result), - pathLocationBoost: getFileLocationBoost(result), - noisePenalty: Math.max(0, Number(result.noisyPathSegmentCount || 0)) * 70, - depthPenalty: getFileDepthPenalty(result), - }, searchQuery, rootSearchRanking); - }) - .filter((candidate): candidate is RootSearchCandidate => Boolean(candidate)); - }, [hasSearchQuery, aiMode, rootBangState, launcherFileResults, fileResultCommands, searchQuery, rootSearchRanking]); + return buildLauncherFileCandidates({ + launcherFileResults, + fileResultCommandByPath, + searchQuery, + rootSearchRanking, + }); + }, [hasSearchQuery, aiMode, rootBangState, launcherFileResults, fileResultCommandByPath, searchQuery, rootSearchRanking]); const browserCandidates = useMemo(() => { if (!browserSearch.enabled || !browserSearch.alphaChromiumRootSearchEnabled || !hasSearchQuery || aiMode || rootBangState.mode !== 'none') return []; diff --git a/src/renderer/src/utils/launcher-file-candidates.ts b/src/renderer/src/utils/launcher-file-candidates.ts new file mode 100644 index 00000000..c64cebe3 --- /dev/null +++ b/src/renderer/src/utils/launcher-file-candidates.ts @@ -0,0 +1,124 @@ +import type { + CommandInfo, + IndexedFileSearchResult, +} from '../../types/electron'; +import { + normalizeRootSearchStableValue, + scoreRootSearchCandidate, + scoreRootSearchFields, + type MatchKind, + type RootSearchCandidate, + type RootSearchRankingState, + type RootSearchSubtype, +} from './root-search-ranking'; + +export function coerceRootSearchMatchKind(value: string | undefined, fallback: MatchKind): MatchKind { + switch (value) { + case 'exact': + case 'alias-exact': + case 'nickname-exact': + case 'prefix': + case 'token-prefix': + case 'compact-prefix': + case 'word-boundary-fuzzy': + case 'contains': + case 'subsequence': + case 'description': + case 'path': + case 'url': + return value; + default: + return fallback; + } +} + +function getFileFreshnessBoost(result: IndexedFileSearchResult): number { + const touched = Math.max(Number(result.mtimeMs || 0), Number(result.birthtimeMs || 0)); + if (!touched) return 0; + const ageHours = Math.max(0, (Date.now() - touched) / (60 * 60 * 1000)); + const ageDays = ageHours / 24; + if (ageHours <= 24) return 120; + if (ageDays <= 7) return 90; + if (ageDays <= 30) return 45; + if (ageDays <= 90) return 15; + return 0; +} + +function getFileLocationBoost(result: IndexedFileSearchResult): number { + const topLevelRoot = String(result.topLevelRoot || '').trim(); + const depth = Number(result.homeRelativeDepth || result.depth || 0); + const protectedRoot = topLevelRoot === 'Desktop' || topLevelRoot === 'Documents' || topLevelRoot === 'Downloads'; + if (!protectedRoot) return 20; + return 120 - Math.min(90, Math.max(0, depth - 2) * 18); +} + +function getFileDepthPenalty(result: IndexedFileSearchResult): number { + const depth = Math.max(0, Number(result.homeRelativeDepth || result.depth || 0)); + if (depth <= 2) return 0; + if (depth <= 4) return (depth - 2) * 25; + return Math.min(260, 50 + (depth - 4) * 35); +} + +export function buildLauncherFileResultCommandByPath( + fileResultCommands: readonly CommandInfo[] +): Map { + const commandByPath = new Map(); + for (const command of fileResultCommands) { + if (typeof command.path !== 'string') continue; + if (!commandByPath.has(command.path)) { + commandByPath.set(command.path, command); + } + } + return commandByPath; +} + +export function buildLauncherFileCandidates({ + launcherFileResults, + fileResultCommandByPath, + searchQuery, + rootSearchRanking, +}: { + launcherFileResults: readonly IndexedFileSearchResult[]; + fileResultCommandByPath: ReadonlyMap; + searchQuery: string; + rootSearchRanking: RootSearchRankingState; +}): RootSearchCandidate[] { + return launcherFileResults + .map((result) => { + const command = fileResultCommandByPath.get(result.path); + if (!command) return null; + const scored = scoreRootSearchFields(searchQuery, [ + { value: result.name, kind: 'label', weight: 1 }, + { value: result.parentPath, kind: 'path', weight: 0.72 }, + { value: result.displayPath, kind: 'path', weight: 0.72 }, + { value: result.path, kind: 'path', weight: 0.68 }, + ]); + if (!scored.matched) return null; + const subtype: RootSearchSubtype = result.isDirectory ? 'folder' : 'file'; + const matchKind = coerceRootSearchMatchKind(result.matchKind, scored.matchKind); + const weakFolderMatch = subtype === 'folder' && (matchKind === 'contains' || matchKind === 'subsequence' || matchKind === 'path'); + const stableKey = `file:${normalizeRootSearchStableValue(result.path)}`; + return scoreRootSearchCandidate({ + command: { + ...command, + rootSearchStableKey: stableKey, + rootSearchSource: 'file', + rootSearchSubtype: subtype, + }, + source: 'file', + subtype, + stableKey, + label: result.name, + description: result.displayPath, + pathOrUrl: result.path, + matchKind, + matchScore: scored.matchScore, + sourceQualityBoost: subtype === 'file' ? 8 : weakFolderMatch ? -10 : 0, + freshnessBoost: getFileFreshnessBoost(result), + pathLocationBoost: getFileLocationBoost(result), + noisePenalty: Math.max(0, Number(result.noisyPathSegmentCount || 0)) * 70, + depthPenalty: getFileDepthPenalty(result), + }, searchQuery, rootSearchRanking); + }) + .filter((candidate): candidate is RootSearchCandidate => Boolean(candidate)); +}