diff --git a/docs-site/docs/features/job-search-bar.md b/docs-site/docs/features/job-search-bar.md index b164d78..e87a4c5 100644 --- a/docs-site/docs/features/job-search-bar.md +++ b/docs-site/docs/features/job-search-bar.md @@ -24,6 +24,8 @@ Search matches job fields with fuzzy ranking: - company/employer - location +By default, very low-relevance matches are hidden so results stay focused on likely intent. + Results are grouped by status sections: - Ready diff --git a/orchestrator/src/client/pages/orchestrator/JobCommandBar.utils.test.ts b/orchestrator/src/client/pages/orchestrator/JobCommandBar.utils.test.ts index 0e064d4..4b9a774 100644 --- a/orchestrator/src/client/pages/orchestrator/JobCommandBar.utils.test.ts +++ b/orchestrator/src/client/pages/orchestrator/JobCommandBar.utils.test.ts @@ -19,7 +19,7 @@ describe("JobCommandBar score helpers", () => { expect(score).toBe(0); }); - it("ranks exact and fuzzy matches above non-matches for a query", () => { + it("keeps only relevant matches when a query is provided", () => { const grouped = groupJobsForCommandBar( [ createJob({ @@ -44,10 +44,22 @@ describe("JobCommandBar score helpers", () => { "backend", ); - expect(grouped.ready.map((job) => job.id)).toEqual([ - "exact", - "fuzzy", - "no-match", - ]); + expect(grouped.ready.map((job) => job.id)).toEqual(["exact", "fuzzy"]); + }); + + it("filters out weak fuzzy matches below the relevance floor", () => { + const grouped = groupJobsForCommandBar( + [ + createJob({ + id: "weak-fuzzy", + title: "Backend Engineer", + employer: "Platform Co", + discoveredAt: "2025-01-02T00:00:00Z", + }), + ], + "bde", + ); + + expect(grouped.ready).toEqual([]); }); }); diff --git a/orchestrator/src/client/pages/orchestrator/JobCommandBar.utils.ts b/orchestrator/src/client/pages/orchestrator/JobCommandBar.utils.ts index 9973cf9..c41f346 100644 --- a/orchestrator/src/client/pages/orchestrator/JobCommandBar.utils.ts +++ b/orchestrator/src/client/pages/orchestrator/JobCommandBar.utils.ts @@ -34,6 +34,7 @@ export const lockLabel: Record = { }; const tokenRegex = /^\s*@([a-z-]*)/i; +const MINIMUM_MATCH_SCORE = 600; const parseTime = (value: string | null) => { if (!value) return Number.NaN; @@ -158,24 +159,29 @@ export const groupJobsForCommandBar = ( other: [], }; - const sorted = [...scopedJobs].sort((a, b) => { - if (normalizedQuery) { - const firstScore = computeJobMatchScore(a, normalizedQuery); - const secondScore = computeJobMatchScore(b, normalizedQuery); - if (firstScore !== secondScore) return secondScore - firstScore; - } + const scoredJobs = normalizedQuery + ? scopedJobs + .map((job) => ({ + job, + score: computeJobMatchScore(job, normalizedQuery), + })) + .filter(({ score }) => score >= MINIMUM_MATCH_SCORE) + : scopedJobs.map((job) => ({ job, score: 0 })); - const first = parseTime(a.discoveredAt); - const second = parseTime(b.discoveredAt); + const sorted = scoredJobs.sort((a, b) => { + if (normalizedQuery && a.score !== b.score) return b.score - a.score; + + const first = parseTime(a.job.discoveredAt); + const second = parseTime(b.job.discoveredAt); if (!Number.isNaN(first) && !Number.isNaN(second)) { return second - first; } if (!Number.isNaN(first)) return -1; if (!Number.isNaN(second)) return 1; - return b.id.localeCompare(a.id); + return b.job.id.localeCompare(a.job.id); }); - for (const job of sorted) { + for (const { job } of sorted) { groups[getCommandGroup(job.status)].push(job); } return groups;