fix(search): match complete paths in file and project search

Treat slash-separated fragments as searchable text and use the same full-path fuzzy matcher for daemon results and remembered project paths.
This commit is contained in:
Mohamed Boudra
2026-08-19 12:18:23 +02:00
parent 91024bc029
commit e55dd5a212
6 changed files with 245 additions and 45 deletions
@@ -32,17 +32,30 @@ describe("buildWorkingDirectorySuggestions", () => {
expect(results).toEqual(["/Users/me/projects/paseo-desktop"]);
});
it("leaves path-query semantics to the daemon", () => {
it("matches recommended paths using the complete path text", () => {
const results = buildWorkingDirectorySuggestions({
recommendedPaths: [
"/Users/me/archive/projects/paseo-desktop",
"/Users/me/projects/paseo-desktop",
],
serverPaths: [],
query: "~/projects/pso",
query: "projects/pso",
});
expect(results).toEqual([]);
expect(results).toEqual([
"/Users/me/archive/projects/paseo-desktop",
"/Users/me/projects/paseo-desktop",
]);
});
it("fuzzy-matches recommended paths using their full path", () => {
const results = buildWorkingDirectorySuggestions({
recommendedPaths: ["/Users/me/projects/blankpage/editor"],
serverPaths: [],
query: "blank page editor",
});
expect(results).toEqual(["/Users/me/projects/blankpage/editor"]);
});
it("treats '~' as an active query and includes daemon suggestions", () => {
@@ -1,4 +1,4 @@
import { scoreMatch } from "@getpaseo/protocol/search/text-match";
import { scorePathMatch } from "@getpaseo/protocol/search/text-match";
export interface BuildWorkingDirectorySuggestionsInput {
recommendedPaths: string[];
@@ -19,9 +19,8 @@ export function buildWorkingDirectorySuggestions(
recommendedPathMatchesQuery(path, query),
);
// The request owner correlates these results with the current query. The
// daemon owns filesystem query parsing, filtering, and ranking; doing it
// again here creates a second search implementation that can disagree.
// Server paths are already ranked by the daemon. Recommended paths use the
// same shared matcher, then keep their existing recommendation order.
return uniquePaths([...matchingRecommended, ...input.serverPaths]);
}
@@ -45,12 +44,8 @@ function recommendedPathMatchesQuery(path: string, query: string): boolean {
if (["~", "~/"].includes(normalizedQuery)) {
return true;
}
if (normalizedQuery.includes("/") || normalizedQuery.startsWith("~")) {
return false;
}
const basename = candidate.split("/").at(-1) ?? "";
return candidate.includes(normalizedQuery) || scoreMatch(normalizedQuery, basename) !== null;
return scorePathMatch(normalizedQuery, candidate) !== null;
}
function normalizePath(value: string): string {