Skip to content

Commit f4683a4

Browse files
committed
fix(search): restore cross-field Cmd-K matches
Routing folder paths through filterAndCap's secondary-rank parameter (#6192) fixed folder text outranking exact name matches, but scoreItem matches the name or the secondary text, never the two together -- so a query spanning both, like "leads emea" for a Leads table in EMEA, stopped matching anything. Falls back to matching the joined text when neither field matches alone. That runs last so a secondary hit still cannot masquerade as a name hit, keeping the ranking #6192 established.
1 parent 25e6091 commit f4683a4

2 files changed

Lines changed: 43 additions & 4 deletions

File tree

apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/search-modal/utils.test.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -315,3 +315,34 @@ describe('filterAndCap', () => {
315315
expect(capped).toEqual(filterAndSort(items, id, 'item').slice(0, MAX_RESULTS_PER_GROUP))
316316
})
317317
})
318+
319+
describe('filterAndSort — name vs. secondary text', () => {
320+
const rows = [
321+
{ name: 'Leads', folder: 'Sales / EMEA' },
322+
{ name: 'Contacts', folder: 'Sales / AMER' },
323+
{ name: 'EMEA', folder: 'Archive' },
324+
]
325+
const run = (search: string) =>
326+
filterAndSort(
327+
rows,
328+
(r) => r.name,
329+
search,
330+
(r) => r.folder
331+
).map((r) => r.name)
332+
333+
it('matches a query spanning the name and the secondary text', () => {
334+
expect(run('leads emea')).toEqual(['Leads'])
335+
})
336+
337+
it('ranks a name match above a secondary-text match', () => {
338+
expect(run('emea')).toEqual(['EMEA', 'Leads'])
339+
})
340+
341+
it('still matches on secondary text alone', () => {
342+
expect(run('amer')).toEqual(['Contacts'])
343+
})
344+
345+
it('drops rows that match neither field nor the two joined', () => {
346+
expect(run('zzz')).toEqual([])
347+
})
348+
})

apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/search-modal/utils.ts

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -244,9 +244,16 @@ const NAME_MATCH_TIER = 1_000_000
244244

245245
/**
246246
* Ranks an item by its name first, falling back to secondary text (ids, aliases,
247-
* option labels) only when the name doesn't match — a name match always wins, so
248-
* an exact name hit isn't diluted by a long secondary string ("Agent" beats
249-
* "Pi Coding Agent" for the query "agent").
247+
* option labels, folder paths) only when the name doesn't match — a name match
248+
* always wins, so an exact name hit isn't diluted by a long secondary string
249+
* ("Agent" beats "Pi Coding Agent" for the query "agent").
250+
*
251+
* When neither field matches alone the two are matched joined, so a query that
252+
* spans both ("leads emea" for a `Leads` table in `EMEA`) still finds the row.
253+
* That is last on purpose: matching the joined text would otherwise let a
254+
* secondary hit masquerade as a name hit. Positions from the joined match do not
255+
* index into either field, which is safe only because `filterAndSort` scores
256+
* with them and discards them — never pass them to a highlighter.
250257
*/
251258
function scoreItem(name: string, extra: string | undefined, search: string): FuzzyResult {
252259
const byName = fuzzyMatch(name, search)
@@ -255,7 +262,8 @@ function scoreItem(name: string, extra: string | undefined, search: string): Fuz
255262
return { matched: true, score: byName.score + NAME_MATCH_TIER, positions: byName.positions }
256263
}
257264
const byExtra = fuzzyMatch(extra, search)
258-
return byExtra.matched ? byExtra : NO_MATCH
265+
if (byExtra.matched) return byExtra
266+
return fuzzyMatch(`${name} ${extra}`, search)
259267
}
260268

261269
/**

0 commit comments

Comments
 (0)