Skip to content

feat(desktop): fast file search with VS Code fuzzy scorer#3136

Merged
saddlepaddle merged 8 commits into
mainfrom
saddlepaddle/fast-file-search
Apr 3, 2026
Merged

feat(desktop): fast file search with VS Code fuzzy scorer#3136
saddlepaddle merged 8 commits into
mainfrom
saddlepaddle/fast-file-search

Conversation

@saddlepaddle
Copy link
Copy Markdown
Collaborator

@saddlepaddle saddlepaddle commented Apr 3, 2026

Summary

  • Replace Fuse.js with VS Code's path-aware fuzzy scorer (scoreItemFuzzy + compareItemsByFuzzyScore) for dramatically better search ranking — filename matches now properly rank above path-only matches
  • Remove 30s TTL index rebuilds (index kept current via file watcher patching)
  • Pre-warm search index on workspace open so first search is instant
  • Encapsulate CommandPalette as a self-contained component with v1/v2 search variants (v2 uses workspaceTrpc direct to host service)
  • Remove cmdk dependency from search dialog — replaced with lightweight radix Dialog + custom keyboard nav
  • Delete dead code: SearchDialog, KeywordSearch, ScopeToggle, useCommandPalette, debounce logic

Test plan

  • Open command palette (Cmd+P), type a filename — results should appear instantly with correct ranking
  • Type "package" — package.json files should rank above files in packages/ directory
  • Verify first search after workspace open is instant (no delay for index build)
  • Arrow keys navigate results, Enter opens file
  • Works in both v1 and v2 workspace views

Summary by cubic

Replaced fuse.js with VS Code’s path‑aware fuzzy scorer for faster, more accurate Quick Open; rebuilt the command palette on Radix + cmdk with v1/v2 variants and pre‑warmed indexes so the first search is instant.

  • New Features

    • Self‑contained CommandPalette with keyboard nav and Enter hint; v1 exposes include/exclude globs, v2 uses workspaceTrpc via useV2FileSearch (limit 50, hook co‑located with the component).
    • cmdk integration with shouldFilter=false to rely on our scorer.
  • Refactors

    • Ported VS Code scorer into workspace-fs and rewrote file/content ranking with scoreItemFuzzy/compareItemsByFuzzyScore; removed fuse.js.
    • Deleted SearchDialog, KeywordSearch, ScopeToggle, and useCommandPalette; index stays current via watcher patches; removed TTL and request debouncing; pre‑warm via getSearchIndex in the host.
    • Re‑exported CommandPrimitive from @superset/ui/command; fixed lint/type errors in fuzzy-scorer.

Written for commit 99bb3e0. Summary will update on new commits.

Summary by CodeRabbit

  • Refactor

    • Simplified command palette behavior and controls
    • Removed scope toggles and legacy quick-open toggling
    • Removed debouncing for more immediate search feedback
  • New Features

    • Introduced a VS Code–style fuzzy scorer for improved file ranking
    • Added background pre-warming of search indexes for faster initial searches
  • Chores

    • Removed obsolete search dialog, keyword-search components, and legacy hook utilities

…e search

Replace Fuse.js with VS Code's path-aware fuzzy scorer (scoreItemFuzzy +
compareItemsByFuzzyScore), remove 30s TTL index rebuilds, pre-warm search
index on workspace open, and encapsulate CommandPalette as a self-contained
component with v1/v2 search variants.

- Port VS Code's fuzzyScorer.ts (MIT) with full item scoring pipeline
- Score filename (label) separately from path with label prefix boost
- Remove fuse.js dependency, SearchDialog, KeywordSearch, ScopeToggle
- Encapsulate CommandPalette: owns query state, dialog UI, search hook
- Add v2 search variant using workspaceTrpc (direct host service)
- Pre-warm search index in WorkspaceFilesystemManager
- Remove debounce from useFileSearch (no longer needed)
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 3, 2026

Caution

Review failed

Pull request was closed or merged during review

📝 Walkthrough

Walkthrough

Moved command-palette state from a shared hook into the component, replaced Fuse.js scoring with a VS Code–style fuzzy scorer, consolidated search/dialog UI into a single CommandPalette implementation, removed several legacy search components/hooks, added useV2FileSearch, and pre-warmed workspace search indexes in the host service.

Changes

Cohort / File(s) Summary
Command Palette Pages
apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/page.tsx, apps/desktop/src/renderer/routes/_authenticated/_dashboard/workspace/$workspaceId/page.tsx
Removed useCommandPalette usage and navigation-driven handlers; introduced local quickOpenOpen state and updated CommandPalette props to the new simplified interface (workspaceId, open, onOpenChange, onSelectFile, variant).
Command Palette Component
apps/desktop/src/renderer/screens/main/components/CommandPalette/CommandPalette.tsx, .../index.ts
Replaced external SearchDialog usage with an internal Radix-based dialog and cmdk CommandPrimitive; component now manages query/filters internally, chooses between useFileSearch (v1) and useV2FileSearch (v2), and exposes a simplified CommandPaletteProps shape.
Removed UI Components
apps/desktop/src/renderer/screens/main/components/SearchDialog/..., .../KeywordSearch/..., .../CommandPalette/components/ScopeToggle/...
Deleted SearchDialog, KeywordSearch, and ScopeToggle implementations and their index re-exports; functionality folded into the CommandPalette.
Removed Hooks
apps/desktop/src/renderer/screens/main/components/CommandPalette/useCommandPalette.ts, .../KeywordSearch/useKeywordSearch.ts
Deleted useCommandPalette (complex multi-workspace search, scope, debouncing, selection/navigation) and useKeywordSearch; state/logic moved into CommandPalette or new search hooks.
V2 File Search Hook
apps/desktop/src/renderer/screens/main/components/CommandPalette/hooks/useV2FileSearch/useV2FileSearch.ts, .../index.ts
Added useV2FileSearch hook that trims query and calls workspaceTrpc.filesystem.searchFiles (limit 50), returns normalized results and isFetching.
V1 File Search Update
apps/desktop/src/renderer/screens/main/components/WorkspaceView/.../useFileSearch.ts
Removed debouncing; TRPC query now uses trimmed query directly and isFetching reflects only query fetch state.
Fuzzy Scorer
packages/workspace-fs/src/fuzzy-scorer.ts
Added VS Code–style fuzzy scoring utilities: prepareQuery, scoreFuzzy, scoreItemFuzzy, compareItemsByFuzzyScore, caching and multi-piece handling for label/description/path scoring and ranking.
Search Infrastructure
packages/workspace-fs/src/search.ts, packages/workspace-fs/src/search.test.ts
Replaced Fuse.js search with the new fuzzy-scorer; changed index shape to SearchIndexEntry[], removed TTL/versioning, updated search/patch/scan logic to score/filter/sort array entries, and adjusted a test to assert order-agnostic membership.
Host Service Pre-warm
packages/host-service/src/runtime/filesystem/filesystem.ts
When creating a new FsHostService, non-blocking call to getSearchIndex({ rootPath, includeHidden: false }) is triggered to pre-warm the search index; errors are suppressed.
Deps & Exports
packages/workspace-fs/package.json, packages/ui/src/components/ui/command.tsx, apps/desktop/src/renderer/stores/tabs/store.ts
Removed fuse.js dependency; exported CommandPrimitive from UI package; minor formatting/control-flow refactor in tabs store only.

Sequence Diagram(s)

sequenceDiagram
  participant User as User
  participant CP as CommandPalette (Renderer)
  participant Hook as useV2FileSearch
  participant TRPC as workspaceTrpc
  participant Host as HostService
  participant FS as Workspace FS Index
  participant Tabs as useTabsStore

  rect rgba(135,206,235,0.5)
    User->>CP: Open palette / type query
    CP->>Hook: call with workspaceId + query
    Hook->>TRPC: filesystem.searchFiles(query, limit)
    TRPC->>Host: IPC request for search
    Host->>FS: ensure index (getSearchIndex pre-warmed)
    Host->>FS: perform search on index
    FS-->>Host: results
    Host-->>TRPC: results
    TRPC-->>Hook: results
    Hook-->>CP: normalized results
  end

  rect rgba(144,238,144,0.5)
    User->>CP: Select file
    CP->>Tabs: addFileViewerPane(workspaceId, filePath)
    Tabs-->>CP: pane added
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Poem

🐰
Hop, I sniff the new command trail,
Hooks tucked close, the component sets sail,
Fuzzy hops map every path,
Index warmed, no aftermath,
Files open quick — carrot celebration! 🥕

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 9.30% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The PR title 'feat(desktop): fast file search with VS Code fuzzy scorer' clearly and concisely describes the main change: replacing Fuse.js with VS Code's fuzzy scorer to improve search performance and ranking.
Description check ✅ Passed The PR description is comprehensive and well-structured, covering summary, features, refactors, test plan, and additional context. It addresses all key aspects of the changes and follows the repository's description template with detailed explanations.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch saddlepaddle/fast-file-search

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Apr 3, 2026

🧹 Preview Cleanup Complete

The following preview resources have been cleaned up:

  • ✅ Neon database branch
  • ✅ Electric Fly.io app

Thank you for your contribution! 🎉

Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No issues found across 21 files

@greptile-apps
Copy link
Copy Markdown

greptile-apps Bot commented Apr 3, 2026

Greptile Summary

This PR replaces Fuse.js with a port of VS Code's scoreItemFuzzy / compareItemsByFuzzyScore pipeline, removes the 30-second TTL rebuild in favour of watcher-driven patching, pre-warms the search index on workspace open, and refactors CommandPalette into a self-contained component with separate v1/v2 search variants. The scorer port is faithful and the architectural direction is sound, but one critical bug and one correctness issue need to be resolved before merging.

  • P0 — file never opens (v1 workspace): onSelectFile={() => {}} is passed to CommandPalette in workspace/$workspaceId/page.tsx. Selecting any result closes the dialog and does nothing; the file is never opened. The v2 page correctly passes openFilePane.
  • P1 — race condition in patchSearchIndexesForRoot: If a file-watcher event fires while the initial index build is in-flight, the event is skipped (correct) but the pending build promise unconditionally writes stale data to the cache when it resolves. The version-counter guard that prevented this in the old code was removed; it needs to be reintroduced.
  • P2 — weakened test: The ordering assertion ("flat file ranks above nested file with same name") was replaced with unordered toContain checks, hiding whether the new scorer actually delivers the advertised ranking improvement.

Confidence Score: 2/5

  • Not safe to merge — the primary user action (select file → open file) is broken for v1 workspaces.
  • The P0 bug means the core ⌘P flow is non-functional in the v1 workspace: users can search and select a file, but nothing happens. The P1 race condition is narrower (only the initial build window) but introduces silent correctness failures. Both need fixing before this lands.
  • workspace/$workspaceId/page.tsx (broken onSelectFile) and packages/workspace-fs/src/search.ts (stale-build race condition).

Important Files Changed

Filename Overview
apps/desktop/src/renderer/routes/_authenticated/_dashboard/workspace/$workspaceId/page.tsx Simplifies CommandPalette integration to local state, but onSelectFile={() => {}} is a no-op — file selection is completely broken for v1 workspaces.
packages/workspace-fs/src/search.ts Replaces Fuse.js with VS Code's fuzzy scorer and simplifies cache from TTL-based to watcher-driven; race condition when a watcher event fires during the initial index build causes the stale build to overwrite the cache.
packages/workspace-fs/src/fuzzy-scorer.ts Clean MIT-licensed port of VS Code's fuzzyScorer.ts with inlined dependencies; scoring and comparison logic is faithful to the original.
apps/desktop/src/renderer/screens/main/components/CommandPalette/CommandPalette.tsx Refactored from a prop-heavy presentational component to a self-contained dialog with internal query state, keyboard navigation, and dual v1/v2 search variants; logic is sound.
apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/useV2FileSearch/useV2FileSearch.ts New hook for v2 workspaces using workspaceTrpc; correctly enabled only when open and query is non-empty, with placeholder data for smooth UX.
packages/workspace-fs/src/search.test.ts Ranking order assertion weakened to unordered containment check, reducing coverage for the core ranking improvement this PR advertises.
packages/host-service/src/runtime/filesystem/filesystem.ts Adds pre-warming of the search index on workspace open via getSearchIndex(...).catch(() => {}) — straightforward and safe; errors are intentionally silenced.
apps/desktop/src/renderer/screens/main/components/WorkspaceView/RightSidebar/FilesView/hooks/useFileSearch/useFileSearch.ts Removes 150ms debounce and staleTime: 1000; each keystroke now fires an immediate IPC query, acceptable given the fast in-memory search backend.
apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/page.tsx Correctly wires CommandPalette to openFilePane; removes cross-workspace navigation (global scope was dropped), which is consistent with the simplified palette.

Sequence Diagram

sequenceDiagram
    participant WM as WorkspaceFilesystemManager
    participant SI as search.ts (getSearchIndex)
    participant FW as FileWatcher (patchSearchIndexesForRoot)
    participant CP as CommandPalette

    WM->>SI: getSearchIndex() [pre-warm]
    Note over SI: buildSearchIndex starts (async)
    SI-->>WM: promise stored in searchIndexBuilds

    FW->>SI: patchSearchIndexesForRoot(events)
    Note over SI: cached=undefined → delete in-flight ref, continue

    Note over SI: buildSearchIndex resolves ⚠️ stale
    SI->>SI: searchIndexCache.set(cacheKey, staleItems)
    Note over SI: file-watcher events lost in cache

    CP->>SI: searchFiles(query)
    SI-->>CP: results from stale index
Loading

Comments Outside Diff (2)

  1. apps/desktop/src/renderer/routes/_authenticated/_dashboard/workspace/$workspaceId/page.tsx, line 91 (link)

    P0 onSelectFile is a no-op — file never opens in v1 workspace

    onSelectFile={() => {}} means that when the user picks a file from the command palette, handleSelectFile is called inside CommandPalette, which closes the dialog and then calls this empty function. The file is never opened. This completely breaks the primary user flow (⌘P → select file → open) for the v1 workspace view.

    The v2 page correctly wires it to openFilePane. The equivalent function should be found in this component (the old commandPalette.selectFile used openTabForFile / navigateToFile). The correct handler needs to be supplied here.

  2. packages/workspace-fs/src/search.test.ts, line 730-733 (link)

    P2 Test weakened: ordering assertion removed

    The original test verified that flatPath ranked before nestedPath — the core behavioral guarantee the PR is trying to improve. The change replaces the ordered toEqual with unordered toContain checks, which means the test no longer catches regressions in ranking.

    The PR description explicitly says:

    package.json files should rank above files in packages/ directory

    The new scorer should produce this ordering (shorter relativePath wins the fallbackCompare tiebreaker when both have the same label score). Restoring the ordering assertion would confirm this:

Reviews (1): Last reviewed commit: "chore: add proper MIT license attributio..." | Re-trigger Greptile

Comment on lines 289 to +293
const buildPromise = buildSearchIndex(options)
.then((index) => {
if (getSearchIndexVersion(cacheKey) === buildVersion) {
searchIndexCache.set(cacheKey, { index, builtAt: Date.now() });
}
.then((items) => {
searchIndexCache.set(cacheKey, items);
searchIndexBuilds.delete(cacheKey);
return index;
return items;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Race condition: stale index overwrites cache when watcher fires during initial build

When patchSearchIndexesForRoot is called while the initial index build is in-flight:

  1. cached is undefined, so searchIndexBuilds.delete(cacheKey) is called and the loop continues — correctly skipping the patch.
  2. However, the background buildSearchIndex promise is still running. When it resolves, it unconditionally calls searchIndexCache.set(cacheKey, items) with the pre-event snapshot.
  3. The watcher event's changes (added/removed files) are permanently lost in the cache until the next watcher event.

The old code protected against this with a version counter (buildVersion / getSearchIndexVersion check in the .then() callback) that prevented a stale build from overwriting the cache. That protection was removed.

A minimal fix is to track a "generation" counter per cache key in the new code and guard the .then() write:

// Increment generation before cancelling the in-flight build
searchIndexVersions.set(cacheKey, (searchIndexVersions.get(cacheKey) ?? 0) + 1);
searchIndexBuilds.delete(cacheKey);

Then in buildSearchIndex.then():

.then((items) => {
    if (currentGeneration === searchIndexVersions.get(cacheKey)) {
        searchIndexCache.set(cacheKey, items);
    }
    searchIndexBuilds.delete(cacheKey);
    return items;
})

Use cmdk primitives for keyboard navigation and item selection while
keeping our custom fixed positioning, 672px width, and shouldFilter=false
to skip cmdk's internal scoring. Add enter key indicator on selected item.
Re-export CommandPrimitive from @superset/ui/command.
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/workspace-fs/src/search.ts (1)

287-296: ⚠️ Potential issue | 🟠 Major

Guard cache writes from stale in-flight builds.

invalidateSearchIndex(), invalidateAllSearchIndexes(), and patchSearchIndexesForRoot() only drop the promise reference; they do not cancel the running scan. This then still writes the stale snapshot into searchIndexCache, so filesystem events that land during an initial build can be lost until the next invalidation.

🛠️ Proposed fix
-	const buildPromise = buildSearchIndex(options)
+	let buildPromise!: Promise<SearchIndexEntry[]>;
+	buildPromise = buildSearchIndex(options)
 		.then((items) => {
-			searchIndexCache.set(cacheKey, items);
-			searchIndexBuilds.delete(cacheKey);
+			if (searchIndexBuilds.get(cacheKey) === buildPromise) {
+				searchIndexCache.set(cacheKey, items);
+				searchIndexBuilds.delete(cacheKey);
+			}
 			return items;
 		})
 		.catch((error) => {
-			searchIndexBuilds.delete(cacheKey);
+			if (searchIndexBuilds.get(cacheKey) === buildPromise) {
+				searchIndexBuilds.delete(cacheKey);
+			}
 			throw error;
 		});
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/workspace-fs/src/search.ts` around lines 287 - 296, The build
promise may become stale after invalidation because
invalidateSearchIndex/invalidateAllSearchIndexes/patchSearchIndexesForRoot only
remove the promise reference; so update the resolution and rejection handlers
for the promise created in buildSearchIndex to first verify that the in-flight
promise is still the current one in searchIndexBuilds for the given cacheKey
(e.g., compare searchIndexBuilds.get(cacheKey) === buildPromise) before writing
to searchIndexCache or deleting the entry; if it is not the same promise, do not
write the stale items to searchIndexCache and only delete the map entry if it
still points to this buildPromise. Ensure the same guard is applied in both the
.then and .catch branches around searchIndexCache.set and
searchIndexBuilds.delete to prevent stale writes and premature deletes.
♻️ Duplicate comments (2)
packages/workspace-fs/src/fuzzy-scorer.ts (2)

348-349: ⚠️ Potential issue | 🟠 Major

Make the scorer cache key unambiguous.

Concatenating label and description without separators lets different items alias to the same cache entry ("ab"+"c" vs "a"+"bc"). When that happens, one file's IItemScore gets reused for another and ranking/highlighting becomes wrong.

🛠️ Proposed fix
-	const cacheHash = `${label}${description ?? ""}${allowNonContiguousMatches}${query.normalized}`;
+	const cacheHash = [
+		label,
+		description ?? "",
+		allowNonContiguousMatches ? "1" : "0",
+		query.normalized,
+	].join("\0");
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/workspace-fs/src/fuzzy-scorer.ts` around lines 348 - 349, The cache
key construction for cacheHash is ambiguous because it concatenates label and
description without separators, causing collisions and incorrect reuse of
IItemScore entries; fix this by building an unambiguous key (e.g., serialize a
tuple/object or insert explicit separators) that includes label, description,
allowNonContiguousMatches and query.normalized (references: cacheHash, label,
description, allowNonContiguousMatches, query.normalized, cache, IItemScore) so
each distinct item yields a unique cache entry and prevents cross-item
score/highlight reuse.

833-858: ⚠️ Potential issue | 🟡 Minor

Keep quoted queries as a single exact-match piece.

expectExactMatch is computed from the full input, but this still splits on spaces before building values. A query like "foo bar" turns into two fuzzy terms instead of one contiguous phrase, so quoted searches can match with arbitrary gaps.

🛠️ Proposed fix
-	const originalSplit = original.split(MULTIPLE_QUERY_VALUES_SEPARATOR);
-	if (originalSplit.length > 1) {
-		for (const originalPiece of originalSplit) {
-			const expectExactMatchPiece = queryExpectsExactMatch(originalPiece);
-			const {
-				pathNormalized: pathNormalizedPiece,
-				normalized: normalizedPiece,
-				normalizedLowercase: normalizedLowercasePiece,
-			} = normalizeQuery(originalPiece);
-
-			if (normalizedPiece) {
-				if (!values) {
-					values = [];
-				}
-
-				values.push({
-					original: originalPiece,
-					originalLowercase: originalPiece.toLowerCase(),
-					pathNormalized: pathNormalizedPiece,
-					normalized: normalizedPiece,
-					normalizedLowercase: normalizedLowercasePiece,
-					expectContiguousMatch: expectExactMatchPiece,
-				});
-			}
-		}
-	}
+	if (!expectExactMatch) {
+		const originalSplit = original.split(MULTIPLE_QUERY_VALUES_SEPARATOR);
+		if (originalSplit.length > 1) {
+			for (const originalPiece of originalSplit) {
+				const expectExactMatchPiece = queryExpectsExactMatch(originalPiece);
+				const {
+					pathNormalized: pathNormalizedPiece,
+					normalized: normalizedPiece,
+					normalizedLowercase: normalizedLowercasePiece,
+				} = normalizeQuery(originalPiece);
+
+				if (normalizedPiece) {
+					if (!values) {
+						values = [];
+					}
+
+					values.push({
+						original: originalPiece,
+						originalLowercase: originalPiece.toLowerCase(),
+						pathNormalized: pathNormalizedPiece,
+						normalized: normalizedPiece,
+						normalizedLowercase: normalizedLowercasePiece,
+						expectContiguousMatch: expectExactMatchPiece,
+					});
+				}
+			}
+		}
+	}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/workspace-fs/src/fuzzy-scorer.ts` around lines 833 - 858, The code
splits the full query by MULTIPLE_QUERY_VALUES_SEPARATOR even when the user
provided a quoted phrase, causing a quoted term like "\"foo bar\"" to be treated
as two fuzzy pieces; to fix, detect quoted queries before splitting (e.g., check
if original starts and ends with a quote or contains an unescaped quoted
segment) and in that case do not split—treat the entire original as one piece,
compute expectExactMatch with queryExpectsExactMatch(original) (or normalized
piece) and push a single value with expectContiguousMatch=true; otherwise
continue splitting and building values as currently done using normalizeQuery
and normalizeLowercase variables.
🧹 Nitpick comments (1)
apps/desktop/src/renderer/screens/main/components/CommandPalette/CommandPalette.tsx (1)

6-7: Co-locate the palette's search adapter next to this component.

Pulling one hook from a route folder and another from the sidebar makes CommandPalette depend on two unrelated UI surfaces. A small local adapter hook would keep this component self-contained and make the new API easier to reuse.
As per coding guidelines, "Co-locate component dependencies (utils, hooks, constants, config, stories) next to the file using them".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/desktop/src/renderer/screens/main/components/CommandPalette/CommandPalette.tsx`
around lines 6 - 7, CommandPalette currently imports two disparate hooks
(useV2FileSearch from the route and useFileSearch from the sidebar) causing
cross-surface coupling; create a new local adapter hook (e.g.,
useCommandPaletteFileSearch) colocated next to CommandPalette.tsx that
normalizes whichever internal search API you need, move any mapping/transform
logic into that hook, update CommandPalette to import only
useCommandPaletteFileSearch (remove direct imports of useV2FileSearch and
useFileSearch), and ensure the new hook exposes the same interface
CommandPalette expects (search term input, results, loading/error states, and
any pagination handlers).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@packages/workspace-fs/src/search.ts`:
- Around line 287-296: The build promise may become stale after invalidation
because
invalidateSearchIndex/invalidateAllSearchIndexes/patchSearchIndexesForRoot only
remove the promise reference; so update the resolution and rejection handlers
for the promise created in buildSearchIndex to first verify that the in-flight
promise is still the current one in searchIndexBuilds for the given cacheKey
(e.g., compare searchIndexBuilds.get(cacheKey) === buildPromise) before writing
to searchIndexCache or deleting the entry; if it is not the same promise, do not
write the stale items to searchIndexCache and only delete the map entry if it
still points to this buildPromise. Ensure the same guard is applied in both the
.then and .catch branches around searchIndexCache.set and
searchIndexBuilds.delete to prevent stale writes and premature deletes.

---

Duplicate comments:
In `@packages/workspace-fs/src/fuzzy-scorer.ts`:
- Around line 348-349: The cache key construction for cacheHash is ambiguous
because it concatenates label and description without separators, causing
collisions and incorrect reuse of IItemScore entries; fix this by building an
unambiguous key (e.g., serialize a tuple/object or insert explicit separators)
that includes label, description, allowNonContiguousMatches and query.normalized
(references: cacheHash, label, description, allowNonContiguousMatches,
query.normalized, cache, IItemScore) so each distinct item yields a unique cache
entry and prevents cross-item score/highlight reuse.
- Around line 833-858: The code splits the full query by
MULTIPLE_QUERY_VALUES_SEPARATOR even when the user provided a quoted phrase,
causing a quoted term like "\"foo bar\"" to be treated as two fuzzy pieces; to
fix, detect quoted queries before splitting (e.g., check if original starts and
ends with a quote or contains an unescaped quoted segment) and in that case do
not split—treat the entire original as one piece, compute expectExactMatch with
queryExpectsExactMatch(original) (or normalized piece) and push a single value
with expectContiguousMatch=true; otherwise continue splitting and building
values as currently done using normalizeQuery and normalizeLowercase variables.

---

Nitpick comments:
In
`@apps/desktop/src/renderer/screens/main/components/CommandPalette/CommandPalette.tsx`:
- Around line 6-7: CommandPalette currently imports two disparate hooks
(useV2FileSearch from the route and useFileSearch from the sidebar) causing
cross-surface coupling; create a new local adapter hook (e.g.,
useCommandPaletteFileSearch) colocated next to CommandPalette.tsx that
normalizes whichever internal search API you need, move any mapping/transform
logic into that hook, update CommandPalette to import only
useCommandPaletteFileSearch (remove direct imports of useV2FileSearch and
useFileSearch), and ensure the new hook exposes the same interface
CommandPalette expects (search term input, results, loading/error states, and
any pagination handlers).

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: ee3292f7-acbe-44a9-8e09-5f51fee541d2

📥 Commits

Reviewing files that changed from the base of the PR and between 9867cf9 and d4ae295.

📒 Files selected for processing (8)
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/useV2FileSearch/useV2FileSearch.ts
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/workspace/$workspaceId/page.tsx
  • apps/desktop/src/renderer/screens/main/components/CommandPalette/CommandPalette.tsx
  • apps/desktop/src/renderer/screens/main/components/CommandPalette/index.ts
  • apps/desktop/src/renderer/stores/tabs/store.ts
  • packages/ui/src/components/ui/command.tsx
  • packages/workspace-fs/src/fuzzy-scorer.ts
  • packages/workspace-fs/src/search.ts
✅ Files skipped from review due to trivial changes (3)
  • packages/ui/src/components/ui/command.tsx
  • apps/desktop/src/renderer/stores/tabs/store.ts
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/useV2FileSearch/useV2FileSearch.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/desktop/src/renderer/screens/main/components/CommandPalette/index.ts

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (1)
apps/desktop/src/renderer/screens/main/components/CommandPalette/CommandPalette.tsx (1)

116-126: ⚠️ Potential issue | 🟡 Minor

Label the include/exclude glob inputs.

These fields rely on placeholder text only. Once the user types, assistive tech loses the field purpose, which makes the filters hard to use non-visually.

♿ Proposed fix
 								<input
+									aria-label="Include files glob pattern"
 									value={includePattern}
 									onChange={(e) => setIncludePattern(e.target.value)}
 									placeholder="files to include (glob)"
 									className="h-8 rounded border bg-transparent px-2 text-xs outline-none placeholder:text-muted-foreground"
 								/>
 								<input
+									aria-label="Exclude files glob pattern"
 									value={excludePattern}
 									onChange={(e) => setExcludePattern(e.target.value)}
 									placeholder="files to exclude (glob)"
 									className="h-8 rounded border bg-transparent px-2 text-xs outline-none placeholder:text-muted-foreground"
 								/>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/desktop/src/renderer/screens/main/components/CommandPalette/CommandPalette.tsx`
around lines 116 - 126, The include/exclude glob inputs in CommandPalette rely
solely on placeholder text so their purpose is lost once typed; update the JSX
for the inputs (the ones using includePattern/setIncludePattern and
excludePattern/setExcludePattern) to provide accessible labels—either add
visually-hidden <label> elements associated via id for each input or add
explicit aria-label attributes (e.g., aria-label="Files to include (glob)" and
aria-label="Files to exclude (glob)") so assistive technology always knows the
field purpose.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@apps/desktop/src/renderer/screens/main/components/CommandPalette/CommandPalette.tsx`:
- Around line 48-49: The component currently derives `results` unconditionally
from `v2Search.results` or `v1Search.searchResults`, which keeps placeholderData
shown when `query` is empty; update the logic that computes `results` (the
`results` binding/variable in `CommandPalette.tsx`) to return an empty array
when `query` (or the local query state) is blank/whitespace (e.g., `query.trim()
=== ""`), otherwise use `v2Search.results` or `v1Search.searchResults`; apply
the same guard wherever results are computed/used (including the other block
around the Enter/action handlers at the lines referenced) so Enter cannot act on
stale placeholder items.

---

Duplicate comments:
In
`@apps/desktop/src/renderer/screens/main/components/CommandPalette/CommandPalette.tsx`:
- Around line 116-126: The include/exclude glob inputs in CommandPalette rely
solely on placeholder text so their purpose is lost once typed; update the JSX
for the inputs (the ones using includePattern/setIncludePattern and
excludePattern/setExcludePattern) to provide accessible labels—either add
visually-hidden <label> elements associated via id for each input or add
explicit aria-label attributes (e.g., aria-label="Files to include (glob)" and
aria-label="Files to exclude (glob)") so assistive technology always knows the
field purpose.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: dac8ee3b-56c8-4657-b58e-ef0eebdab14c

📥 Commits

Reviewing files that changed from the base of the PR and between d4ae295 and 0c10e9e.

📒 Files selected for processing (3)
  • apps/desktop/src/renderer/screens/main/components/CommandPalette/CommandPalette.tsx
  • apps/desktop/src/renderer/screens/main/components/CommandPalette/hooks/useV2FileSearch/index.ts
  • apps/desktop/src/renderer/screens/main/components/CommandPalette/hooks/useV2FileSearch/useV2FileSearch.ts
✅ Files skipped from review due to trivial changes (1)
  • apps/desktop/src/renderer/screens/main/components/CommandPalette/hooks/useV2FileSearch/index.ts

Comment on lines +48 to +49
const results = variant === "v2" ? v2Search.results : v1Search.searchResults;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Don't render placeholder matches for an empty query.

Both search hooks preserve prior matches via placeholderData, so deriving results unconditionally means backspacing to blank — or reopening after setQuery("") — can leave the previous hit list visible with no active search. That also leaves Enter acting on stale items.

🩹 Proposed fix
+	const hasQuery = query.trim().length > 0;
-	const results = variant === "v2" ? v2Search.results : v1Search.searchResults;
+	const results = hasQuery
+		? variant === "v2"
+			? v2Search.results
+			: v1Search.searchResults
+		: [];
...
-							{results.length === 0 && (
+							{hasQuery && results.length === 0 && (

Also applies to: 131-136

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/desktop/src/renderer/screens/main/components/CommandPalette/CommandPalette.tsx`
around lines 48 - 49, The component currently derives `results` unconditionally
from `v2Search.results` or `v1Search.searchResults`, which keeps placeholderData
shown when `query` is empty; update the logic that computes `results` (the
`results` binding/variable in `CommandPalette.tsx`) to return an empty array
when `query` (or the local query state) is blank/whitespace (e.g., `query.trim()
=== ""`), otherwise use `v2Search.results` or `v1Search.searchResults`; apply
the same guard wherever results are computed/used (including the other block
around the Enter/action handlers at the lines referenced) so Enter cannot act on
stale placeholder items.

@saddlepaddle saddlepaddle merged commit 589a7c7 into main Apr 3, 2026
14 of 15 checks passed
MocA-Love added a commit to MocA-Love/superset that referenced this pull request Apr 5, 2026
…e search

Hybrid approach from upstream 589a7c7 (superset-sh#3136):
- Add fuzzy-scorer.ts (VS Code's path-aware fuzzy scoring, MIT licensed)
- Replace Fuse.js fallback in searchFiles() with scoreItemFuzzy/compareItemsByFuzzyScore
- Keep exact-match fast path, content search, replace API, and all UI untouched
- Keep Fuse.js import for backward compat (still used by FileSearchIndex maps)
MocA-Love added a commit to MocA-Love/superset that referenced this pull request Apr 5, 2026
cherry-pick方式で内容を取り込み済みの14コミットをgit履歴上もマージ済みにする。

取り込み済み (cherry-pick / 手動移植):
- be22b46 superset-sh#3125 — スキップ (下記参照)
- 88bc7fb superset-sh#3127 — Revert DA1 ✓
- 92d0ff9 superset-sh#3054 — DA1 fix ✓
- c48450e superset-sh#3093 — file viewer pane fix ✓
- fffa8db superset-sh#3128 — version 1.4.7 ✓
- 589a7c7 superset-sh#3136 — fuzzy scorer (ハイブリッド方式) ✓
- ceb8c81 superset-sh#3150 — Electron 40.8.5 ✓
- 8922b94 superset-sh#3137 — terminalId分離 ✓
- c7508e5 superset-sh#3152 — GitHub無料化 ✓
- 2b91f11 superset-sh#3155 — v2 terminal theme ✓
- b8b11af superset-sh#3154 — TUI dimension fix ✓
- 7599ace superset-sh#3149 — v2 sidebar file tree (手動統合) ✓
- 4d7c612 superset-sh#3174 — DnD重複削除 ✓
- 864977d superset-sh#3157 — Host Service分離 ✓

意図的にスキップ:
- be22b46 superset-sh#3125 (GitHub polling簡素化)
  フォーク独自のGitHubSyncService (バックエンド集中ポーリング) と
  設計が異なるため不採用。upstreamはフロントエンドhover駆動、フォークは
  バックエンドキャッシュウォーマー方式。詳細は githubQueryPolicy.ts と
  github-sync-service.ts のFORK NOTEを参照。

ゴースト・マージ復元 (revert 134cfd5 で消失した内容):
- 538f306 superset-sh#3120 — Patch vuln ✓
- 1588d20 superset-sh#3108 — terminal lifecycle分離 ✓
- 59426f6 superset-sh#3122 — file tree + FilePane + Alert refactor (手動統合) ✓
- 10d9a5d superset-sh#3097 — tiptap line-height ✓
- 337a9ae superset-sh#3121 — Codex hooks削除 ✓
@Kitenite Kitenite deleted the saddlepaddle/fast-file-search branch May 6, 2026 04:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant