feat: add fuzzy search with fuse.js#3829
Conversation
…anking Enhance the search functionality to support flexible multi-word queries and better match results: Backend changes: - Add spec titles to /plots/filter API response for richer search context - Update _collect_all_images to include title field in image dicts Frontend changes: - Implement multi-word search: all query words must match but can appear anywhere in the value (not necessarily consecutive) - Add relevance scoring to prioritize exact matches, prefix matches, and substring matches over multi-word matches - Add title field to PlotImage interface for future search enhancements Examples of improved matching: - "scatter basic" now matches: scatter-basic, basic-scatter, scatter-basic-3d - "bar horiz" now matches: bar-horizontal, bar-grouped-horizontal - Results ranked by relevance: exact > starts-with > contains > multi-word Fixes the issue where searching for two short words only worked with exact consecutive matches.
- Add fuse.js for typo-tolerant fuzzy matching (threshold: 0.3) - Enable extended search for multi-word AND queries - Search spec titles in addition to spec_id - Show spec title as tooltip in dropdown - Add "fuzzy" divider label between exact and fuzzy matches - Add specTitles mapping to API response Closes #3828 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
This PR adds fuzzy search functionality to the filter system using fuse.js, enabling typo-tolerant matching for filter values. The implementation includes backend support for spec titles and frontend UI enhancements to display exact vs. fuzzy matches with tooltips.
Changes:
- Added fuse.js dependency for fuzzy search with configurable thresholds (0.3 for matching, 0.1 for exact classification)
- Introduced spec title search alongside spec_id matching and visual separators between exact/fuzzy results
- Extended backend API to return spec titles mapping for enhanced search context
Reviewed changes
Copilot reviewed 11 out of 12 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| app/package.json | Added fuse.js ^7.1.0 dependency |
| app/yarn.lock | Lock file update for fuse.js |
| app/src/utils/fuzzySearch.ts | New fuzzy search utility with fuse.js configuration and match type classification |
| app/src/utils/filters.ts | Refactored getSearchResults to use fuzzy matching with exact/fuzzy result grouping |
| app/src/utils/index.ts | Export fuzzySearch utilities |
| app/src/types/index.ts | Added title field to PlotImage and specTitles to FilteredPlotsResponse |
| app/src/components/FilterBar.tsx | Updated to display fuzzy results with divider and spec title tooltips |
| app/src/hooks/useFilterState.ts | Pass through specTitles from API response |
| app/src/hooks/useFilterFetch.ts | Fetch and manage specTitles state |
| api/schemas.py | Added specTitles field to FilteredPlotsResponse schema |
| api/routers/plots.py | Generate spec_id to title mapping and include title in image dicts |
| /** | ||
| * Fuzzy search utilities using fuse.js. | ||
| * | ||
| * Provides typo-tolerant search for filter values. | ||
| */ |
There was a problem hiding this comment.
The new fuzzySearch utility module lacks test coverage. Given that this is core search functionality with specific threshold configurations (FUZZY_THRESHOLD=0.3, EXACT_THRESHOLD=0.1), tests should verify the match type classification logic and ensure typo tolerance works as expected (e.g., 'scater' → 'scatter').
| export function getSearchResults( | ||
| filterCounts: FilterCounts | null, | ||
| activeFilters: ActiveFilters, | ||
| searchQuery: string, | ||
| selectedCategory: FilterCategory | null | ||
| ): { category: FilterCategory; value: string; count: number }[] { | ||
| if (!filterCounts) return []; | ||
| selectedCategory: FilterCategory | null, | ||
| specTitles: Record<string, string> = {} | ||
| ): SearchResult[] { |
There was a problem hiding this comment.
The refactored getSearchResults function now includes fuzzy matching logic and spec title searching, but there are no tests for this updated behavior. Tests should verify that exact matches are sorted before fuzzy matches, that spec titles are searched for the 'spec' category, and that the matchType field is correctly assigned.
- Add vitest as test framework - Add tests for fuzzySearch.ts (match type, typo tolerance) - Add tests for getSearchResults (sorting, filtering, spec titles) - All 19 tests passing Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add test-frontend job to ci-tests.yml - Add @vitest/coverage-v8 for coverage reporting - Add vitest.config.ts with coverage configuration - Upload frontend coverage to Codecov with 'frontend' flag - Only runs when app/ files change - Add app/coverage/ to .gitignore Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
| keys: ['value', 'title'], | ||
| threshold: FUZZY_THRESHOLD, | ||
| distance: 100, | ||
| minMatchCharLength: 1, |
There was a problem hiding this comment.
Setting minMatchCharLength: 1 can lead to many false positives with single-character searches. Consider setting this to at least 2 to improve search quality and performance, especially for large datasets. Single-character searches are rarely useful for finding specific plot specifications.
| minMatchCharLength: 1, | |
| minMatchCharLength: 2, |
| return specTitle ? ( | ||
| <Tooltip key={`${category}-${value}`} title={specTitle} placement="right" arrow> | ||
| <span>{menuItem}</span> | ||
| </Tooltip> | ||
| ) : ( | ||
| menuItem | ||
| ); |
There was a problem hiding this comment.
Wrapping MenuItem in a <span> is necessary for Tooltip to work, but this duplicates the key attribute (it's already on menuItem at line 713). The wrapping span should not have a key prop. Remove the key from the ternary expression at line 745 since the menuItem already has the unique key.
| uses: codecov/codecov-action@v5 | ||
| with: | ||
| token: ${{ secrets.CODECOV_TOKEN }} | ||
| files: ./app/coverage/coverage-final.json |
There was a problem hiding this comment.
The coverage file path should be relative to the working directory. Since the test runs in working-directory: app, the path should be ./coverage/coverage-final.json (without the app/ prefix), or the working-directory should be removed from the codecov action. Verify that coverage upload works correctly in CI.
Summary
Configuration
Test plan
Closes #3828
🤖 Generated with Claude Code