perf(feed): memoize search haystack index to avoid per-query rebuild - #295
Open
guyghost wants to merge 1 commit into
Open
perf(feed): memoize search haystack index to avoid per-query rebuild#295guyghost wants to merge 1 commit into
guyghost wants to merge 1 commit into
Conversation
The feed store rebuilt each mission's search haystack (concatenating and
lowercasing title/client/description/location/source/stack) on every
search keystroke. The haystack only depends on the missions, not the
query, so typing in the search box was doing O(n) string allocation work
per keystroke over the full feed.
Split into two $derived layers:
- missionHaystacks: query-independent index, recomputes only when the
missions array changes (new scan / reload).
- filteredMissions: query-dependent filter that does a cheap .includes()
per mission against the prebuilt haystacks.
Behavior is identical: same field set and ordering (title -> client ->
description -> location -> source -> stack), same "string && length > 0"
guards, same .join(' ').toLowerCase() normalization, same substring
match, and same empty-query short-circuit.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
1 Skipped Deployment
|
There was a problem hiding this comment.
Pull request overview
This PR improves side panel feed search performance by memoizing each mission’s precomputed, lowercased “search haystack” so it’s rebuilt only when the mission list changes, rather than on every search keystroke.
Changes:
- Extracted query-independent haystack construction into
buildSearchHaystack(mission)and memoized it via a$derivedarray (missionHaystacks). - Switched query-dependent filtering to a
$derived.bythat performs only a per-mission.includes()against the prebuilt haystacks. - Kept the searchable field set and ordering consistent with the prior implementation (title → client → description → location → source → stack).
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Memoizes the feed search haystack so it is rebuilt only when missions change, not on every search keystroke.
Previously,
createFeedStore()rebuilt each mission's search haystack — concatenating and lowercasingtitle/client/description/location/source/stack— on every search keystroke, for every mission in the feed. The haystack depends only on the missions, not the query, so this was wasted O(n) string allocation per keystroke.Change
Split the single
$derivedinto two layers inapps/extension/src/lib/state/feed.svelte.ts:missionHaystacks($derived): query-independent index of lowercased haystacks. Recomputes only when themissionsarray changes (new scan / reload).filteredMissions($derived.by): query-dependent filter that runs a cheap.includes()per mission against the prebuilt haystacks.Behavioral parity
Identical matching behavior — verified by the existing
tests/unit/state/feed.test.tssuite (11/11 pass unchanged):title → client → description → location → source → stacktypeof value === 'string' && value.length > 0guards.join(' ').toLowerCase()normalization.includes) matchVerification
pnpm --filter @pulse/extension exec vitest run tests/unit/state/feed.test.ts→ 11/11 passpnpm --filter @pulse/extension typecheck→ cleanpnpm --filter @pulse/extension lint→ 0 errorspnpm ci:checkgate → green (format:check && lint && typecheck && test && build)Context: 10 performance improvements surveyed
This PR addresses the highest-impact / lowest-effort item from a survey of 10 performance improvement candidates in the extension. The other nine (documented internally) are candidates for follow-up work — e.g. per-profile batch scoring context, location normalization caching, lazy UI mount, feed aggregates fusion,
stripHtmlconsolidation, dedup canonical-score cache.