Skip to content

Commit 3107b1b

Browse files
committed
perf(search): scope the result cap to active queries, never the browse list
Keep the empty state byte-for-byte identical to before — capping applies only to the top-ranked matches of an active query (the reshuffling per-keystroke render that stalls input), never to the full browsable list. No browsable result a user could otherwise see is hidden.
1 parent 567c5c6 commit 3107b1b

2 files changed

Lines changed: 20 additions & 9 deletions

File tree

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

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -245,11 +245,18 @@ describe('fuzzyMatch — positions for highlighting', () => {
245245
describe('filterAndCap', () => {
246246
const id = (s: string) => s
247247

248-
it('caps the result set to MAX_RESULTS_PER_GROUP', () => {
248+
it('caps an active search to MAX_RESULTS_PER_GROUP', () => {
249249
const items = Array.from({ length: MAX_RESULTS_PER_GROUP + 25 }, (_, i) => `item ${i}`)
250250
expect(filterAndCap(items, id, 'item')).toHaveLength(MAX_RESULTS_PER_GROUP)
251251
})
252252

253+
it('never caps the empty (browse) state, even above the cap', () => {
254+
const items = Array.from({ length: MAX_RESULTS_PER_GROUP + 25 }, (_, i) => `item ${i}`)
255+
const result = filterAndCap(items, id, '')
256+
expect(result).toHaveLength(items.length)
257+
expect(result).toBe(items)
258+
})
259+
253260
it('returns every match untrimmed when under the cap', () => {
254261
const items = ['Slack', 'Slate', 'Slalom']
255262
expect(filterAndCap(items, id, 'sl')).toHaveLength(3)

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

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -259,18 +259,22 @@ export function filterAndSort<T>(items: T[], toValue: (item: T) => string, searc
259259
}
260260

261261
/**
262-
* Upper bound on rendered rows per result group. The palette can hold thousands
263-
* of items (1,000+ tool operations, plus large workspaces), and re-rendering the
264-
* full set on every keystroke is what stalls the input as results reshuffle.
265-
* Results are score-sorted, so capping only trims the low-relevance tail while
266-
* keeping the DOM — and each keystroke's reconciliation — bounded.
262+
* Upper bound on rendered rows per result group while searching. Re-rendering an
263+
* unbounded, reshuffling match set on every keystroke — the catalog alone is
264+
* 1,000+ tool operations — is what stalls the input and drops the next
265+
* character. Results are score-sorted, so this only trims the low-relevance tail
266+
* of a query.
267267
*/
268268
export const MAX_RESULTS_PER_GROUP = 50
269269

270270
/**
271-
* {@link filterAndSort} capped to {@link MAX_RESULTS_PER_GROUP} rows so no single
272-
* group can flood the DOM and block typing.
271+
* {@link filterAndSort}, but bounded to {@link MAX_RESULTS_PER_GROUP} rows *while
272+
* searching* so the per-keystroke render can't flood the DOM and block typing.
273+
* The empty state is returned in full — capping applies only to the top-ranked
274+
* matches of an active query, never to the browsable list, so no result the user
275+
* could otherwise see is hidden.
273276
*/
274277
export function filterAndCap<T>(items: T[], toValue: (item: T) => string, search: string): T[] {
275-
return filterAndSort(items, toValue, search).slice(0, MAX_RESULTS_PER_GROUP)
278+
const results = filterAndSort(items, toValue, search)
279+
return search ? results.slice(0, MAX_RESULTS_PER_GROUP) : results
276280
}

0 commit comments

Comments
 (0)