Skip to content

feat: Add fuzzy search for slash commands#685

Open
scriptease wants to merge 2 commits intoRunMaestro:rcfrom
scriptease:rc
Open

feat: Add fuzzy search for slash commands#685
scriptease wants to merge 2 commits intoRunMaestro:rcfrom
scriptease:rc

Conversation

@scriptease
Copy link
Copy Markdown

@scriptease scriptease commented Mar 29, 2026

Fuzzy slash command matching in action

Hi, I enjoy your project!

I wanted to add fuzzy search to the slash command for skills after having to type the speckit[dot] prefix way too many times.

I reused the fuzzy search used by the @ command to filter files. I made a styling that in my opinion should fit all themes but I am happy to change it to whatever or even no highlighting at all like the @ command".

I messed up trying to push to main in #678, so here is the next attempt, sorry about that!

I hope you like it!

Florian

Summary

  • Fuzzy matching for slash commands: Type abbreviations like /splan to match /speckit.plan, /ohelp to match /openspec.help, etc. Uses the same fuzzyMatchWithScore engine that powers @ mention file completion.
  • Match highlighting: Matched characters render in bold with non-matched characters slightly dimmed (80% opacity), giving clear visual feedback on what's matching.
  • Sorted by relevance: Results are ranked by match quality — exact prefixes first, then boundary-anchored matches, then general subsequences.
  • Added . as a word boundary character in fuzzyMatchWithScore, so dot-separated command names (e.g., speckit.plan) get proper boundary bonuses.
  • Added fuzzyMatchWithIndices utility to search.ts for highlight rendering.
  • Applied consistently across desktop (InputArea.tsx, useInputKeyDown.ts) and mobile (SlashCommandAutocomplete.tsx).

Test plan

  • Tests added to search.test.ts covering fuzzy matching for slash commands and fuzzyMatchWithIndices
  • Type /splan → should show /speckit.plan with s, p, l, a, n bolded
  • Type /hist → should show /history as top match (prefix still works)
  • Type / → should show all commands (no filter)
  • Arrow keys and Tab/Enter selection still work correctly
  • Verify on mobile web interface

Mobile version

Fuzzy slash command matching in action mobile

Summary by CodeRabbit

  • New Features

    • Slash command autocomplete now uses fuzzy, boundary-aware matching and highlights matched characters for clearer suggestions.
  • Bug Fixes

    • Selection now clamps to the last valid entry when filtered results shrink, preventing invalid selections and unexpected behavior.
    • Tab/Enter selection guards against empty result sets.
  • Tests

    • Expanded tests for fuzzy matching, boundary behavior, highlight rendering, and updated filtering/selection expectations.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 29, 2026

📝 Walkthrough

Walkthrough

Replace prefix-only slash-command filtering with fuzzy subsequence matching, add boundary-aware scoring and index-based highlighting, update UI and hook logic to use the new utilities, and adjust tests to tolerate highlighted/split DOM rendering and updated selected-index clamping.

Changes

Cohort / File(s) Summary
Search Utilities
src/renderer/utils/search.ts
Added fuzzyMatchWithIndices(text, query, extraBoundaryChars?); extended fuzzyMatchWithScore(..., extraBoundaryChars?); added filterSlashCommands(...) and highlightSlashCommand(...) (returns React.ReactNode).
UI Components
src/renderer/components/InputArea.tsx, src/web/mobile/SlashCommandAutocomplete.tsx
Replaced prefix-only filtering with filterSlashCommands using normalized query (strip leading /); render labels via highlightSlashCommand; memoized filtering and changed selected-index clamping to last valid index when shrinking.
Input Hook
src/renderer/hooks/input/useInputKeyDown.ts
Use normalized query and filterSlashCommands for autocomplete lookup; clamp selectedSlashCommandIndex to valid range before selecting; early-return when no matches.
Tests — Search Logic
src/__tests__/renderer/utils/search.test.ts
Added tests for fuzzyMatchWithIndices (empty/non-match/oversize query, prefix and boundary-aware matches, greedy fallback) and extended fuzzyMatchWithScore tests for boundary-aware scoring and dot-boundary behavior.
Tests — Components / Hooks
src/__tests__/renderer/components/InputArea.test.tsx, src/__tests__/web/mobile/SlashCommandAutocomplete.test.tsx, src/__tests__/renderer/hooks/useInputKeyDown.test.ts
Updated assertions to match command text via el?.textContent === '<command>' predicate to tolerate split/highlighted spans; adjusted a test input and expected clamping behavior in hook/mobile tests.
Mobile Component Tests
src/__tests__/web/mobile/SlashCommandAutocomplete.test.tsx
Changed expected clamping result to last valid index when filter changes; adapted text queries to predicate-based matching for split spans.

Sequence Diagram(s)

sequenceDiagram
  participant Input as InputArea / Mobile UI
  participant Hook as useInputKeyDown
  participant Utils as search.ts (filter/highlight)
  participant Renderer as DOM Renderer

  Input->>Hook: user types "/..." (inputValue)
  Hook->>Utils: normalize query (strip '/'), call filterSlashCommands(query, isTerminalMode)
  Utils->>Utils: fuzzyMatchWithScore & fuzzyMatchWithIndices (boundary-aware)
  Utils-->>Hook: return sorted filteredCommands
  Hook->>Renderer: provide filteredCommands + highlighted labels (React nodes)
  Renderer->>Input: render dropdown (may split text into spans)
  Input->>Hook: user activates selection (Tab/Enter)
  Hook->>Hook: clamp index -> pick command -> apply selection
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Poem

I hop through letters, dots, and scores,
Finding matches behind UI doors,
Indices light each tiny span,
Fuzzy trails for a searching rabbit's plan,
Cheers to highlights and ranked detours! 🐇✨

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: Add fuzzy search for slash commands' accurately captures the main change: implementing fuzzy matching for slash command autocomplete across the codebase.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

@scriptease scriptease changed the title Rc feat: Add fuzzy search for slash commands Mar 29, 2026
@scriptease scriptease force-pushed the rc branch 2 times, most recently from 5ac9ead to 76fc248 Compare March 29, 2026 09:28
@scriptease scriptease marked this pull request as ready for review March 29, 2026 09:43
@greptile-apps
Copy link
Copy Markdown

greptile-apps bot commented Mar 29, 2026

Greptile Summary

This PR adds fuzzy matching to slash command autocomplete across desktop (InputArea.tsx, useInputKeyDown.ts) and mobile (SlashCommandAutocomplete.tsx), using the existing fuzzyMatchWithScore engine (extended with an optional extraBoundaryChars parameter) and a new fuzzyMatchWithIndices utility for highlight rendering. The implementation is correct and well-tested.

Key changes:

  • search.ts: Adds fuzzyMatchWithIndices (boundary-preferring index extraction with a canMatchRest lookahead to prevent dead-ends) and extends fuzzyMatchWithScore with optional dot boundary support.
  • Desktop + mobile filtering: replaces simple .startsWith() with a fuzzy-match → score-sort pipeline, consistently applied in all three relevant files.
  • Highlight rendering: matched characters are bolded; unmatched characters are dimmed to 80% opacity.
  • Clamp-on-out-of-bounds: when the filtered list shrinks below selectedIndex, the selection now clamps to the last valid item instead of resetting to 0.
  • Tests: new fuzzyMatchWithIndices coverage plus slash-command ranking tests; existing tests updated for span-split text and revised clamp behavior.

Minor style concerns:

  • The highlight-rendering IIFE is duplicated verbatim in InputArea.tsx and SlashCommandAutocomplete.tsx.
  • The fuzzy-filter + sort pipeline is duplicated between InputArea.tsx and useInputKeyDown.ts.
  • shouldFuzzyFilter in SlashCommandAutocomplete.tsx is redundant in the useMemo dependency array.

Confidence Score: 5/5

Safe to merge — all findings are P2 style/maintainability suggestions with no correctness or data-integrity impact.

The core algorithms are logically sound and consistent. The feature is well-tested with edge cases covered. All remaining issues are code-duplication and redundant-dep style concerns that don't affect runtime correctness.

No files require special attention. The duplicated pipeline in useInputKeyDown.ts vs InputArea.tsx is worth consolidating before future modifications.

Important Files Changed

Filename Overview
src/renderer/utils/search.ts Adds fuzzyMatchWithIndices and extends fuzzyMatchWithScore with optional dot boundary support. Both algorithms are correct and consistent.
src/renderer/components/InputArea.tsx Replaces prefix-only filtering with fuzzy-match-and-sort pipeline and adds inline highlight rendering via an IIFE. Highlight IIFE is duplicated in mobile component (P2).
src/renderer/hooks/input/useInputKeyDown.ts Replicates fuzzy-filter pipeline from InputArea.tsx for keyboard navigation; adds safe index clamping on Tab/Enter. Duplicated pipeline creates future drift risk (P2).
src/web/mobile/SlashCommandAutocomplete.tsx Adds fuzzy matching + highlight rendering for mobile. shouldFuzzyFilter is a redundant useMemo dependency (P2). Highlight IIFE duplicated from desktop component (P2).
src/tests/renderer/utils/search.test.ts Adds thorough tests for fuzzyMatchWithIndices and slash-command fuzzy ranking scenarios.
src/tests/web/mobile/SlashCommandAutocomplete.test.tsx Updates text matchers for span-split rendering and corrects out-of-bounds clamp expectation.
src/tests/renderer/components/InputArea.test.tsx Updates text query matchers to handle span-split rendering from fuzzy highlighting.
src/tests/renderer/hooks/useInputKeyDown.test.ts Updates aiOnly filter test to use /run instead of /r since fuzzy matching broadens what /r matches.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A["User types in input (e.g. /splan)"] --> B["Extract query\nquery = inputValue.replace(/^\//, '')"]\n    B --> C{query empty?}\n    C -- yes --> D["Show all commands\nscore = 0 for all"]\n    C -- no --> E["fuzzyMatchWithScore\n(cmd.slice(1), query, '.')"]\n    E --> F{matches?}\n    F -- no --> G["Filter out command"]\n    F -- yes --> H["Keep cmd with score"]\n    H --> I["Sort by score desc"]\n    I --> J["Render filtered list"]\n    J --> K["For each visible cmd:\nfuzzyMatchWithIndices"]\n    K --> L{indices found?}\n    L -- yes --> M["Render chars as bold/dim spans"]\n    L -- no --> N["Render plain command text"]\n    M --> O["Display highlighted autocomplete"]\n    N --> O
Loading

Reviews (1): Last reviewed commit: "Address code review issues:" | Re-trigger Greptile

return cmd.command.toLowerCase().startsWith(inputValue.toLowerCase());
});
// Filter commands based on input and mode (fuzzy matching)
const shouldFuzzyFilter = inputValue && inputValue.startsWith('/');
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Redundant shouldFuzzyFilter in useMemo deps

shouldFuzzyFilter is a derived boolean directly from inputValue (inputValue && inputValue.startsWith('/')), which is already listed as a useMemo dependency. Including shouldFuzzyFilter in the deps array is redundant — inputValue changing will always cause shouldFuzzyFilter to update and the memo to re-run anyway.

Moving shouldFuzzyFilter inside the useMemo makes the dependency explicit and removes the redundant dep entry.

Comment on lines +209 to +224
const query = inputValue.toLowerCase().replace(/^\//, '');
const filteredCommands = allSlashCommands
.filter((cmd) => {
if ('terminalOnly' in cmd && cmd.terminalOnly && !isTerminalMode) return false;
if ('aiOnly' in cmd && cmd.aiOnly && isTerminalMode) return false;
return true;
})
.map((cmd) => {
const { matches, score } = query
? fuzzyMatchWithScore(cmd.command.slice(1), query, '.')
: { matches: true, score: 0 };
return { cmd, matches, score };
})
.filter(({ matches }) => matches)
.sort((a, b) => b.score - a.score)
.map(({ cmd }) => cmd);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Filtering logic duplicated between useInputKeyDown and InputArea

The full fuzzy-filter + sort pipeline (filter by mode → fuzzyMatchWithScore → sort by score) is reproduced identically here and in InputArea.tsx's filteredSlashCommands memo. Both blocks read from the same allSlashCommands/slashCommands prop and produce the same ordered list.

If these two ever diverge (e.g. one adds a tie-breaker or a secondary filter), pressing Tab/Enter could select a different command than the one visually highlighted — a subtle but confusing UX bug.

A shared selector or utility function (e.g. getFilteredSlashCommands(commands, inputValue, isTerminalMode)) would keep both in lockstep and reduce surface area for future drift.

Comment on lines 537 to 564
onMouseEnter={() => setSelectedSlashCommandIndex(idx)}
>
<div className="font-mono text-sm">{cmd.command}</div>
<div className="font-mono text-sm">
{(() => {
const query = inputValueLower.replace(/^\//, '');
if (!query) return cmd.command;
// Match indices on the part after "/", then offset by 1 for the "/"
const indices = new Set(
fuzzyMatchWithIndices(cmd.command.slice(1).toLowerCase(), query, '.').map(
(i) => i + 1
)
);
if (indices.size === 0) return cmd.command;
return Array.from(cmd.command).map((ch, i) =>
indices.has(i) ? (
<span key={i} style={{ fontWeight: 700 }}>
{ch}
</span>
) : (
<span key={i} style={{ opacity: 0.8 }}>
{ch}
</span>
)
);
})()}
</div>
<div className="text-xs opacity-70 mt-0.5">{cmd.description}</div>
</button>
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Duplicated highlight-rendering IIFE across desktop and mobile

The IIFE that calls fuzzyMatchWithIndices and maps characters to <span> elements is copy-pasted verbatim into both InputArea.tsx (here) and SlashCommandAutocomplete.tsx (lines 305–325). Any future changes to highlight style, accessibility attributes, or the index-offset logic need to be applied in two places.

Consider extracting this into a small shared helper, for example a renderHighlightedCommand(command, query) function in a shared utility file. Both components then reduce to a single call: {renderHighlightedCommand(cmd.command, query)}.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Copy link
Copy Markdown

@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

Caution

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

⚠️ Outside diff range comments (1)
src/renderer/hooks/input/useInputKeyDown.ts (1)

226-236: ⚠️ Potential issue | 🟠 Major

Clamp stale slash-command indices before every use.

Line 228 can write -1 when filteredCommands is empty, and Line 231 still decrements stale high values one press at a time after the list shrinks. Desktop only hides that with safeSelectedIndex during render, so Arrow navigation can get stuck on invisible entries and Enter/Tab can no-op after an empty-result ArrowDown.

Suggested fix
 				if (e.key === 'ArrowDown') {
 					e.preventDefault();
-					setSelectedSlashCommandIndex((prev) => Math.min(prev + 1, filteredCommands.length - 1));
+					setSelectedSlashCommandIndex((prev) =>
+						Math.max(0, Math.min(prev + 1, filteredCommands.length - 1))
+					);
 				} else if (e.key === 'ArrowUp') {
 					e.preventDefault();
-					setSelectedSlashCommandIndex((prev) => Math.max(prev - 1, 0));
+					setSelectedSlashCommandIndex((prev) =>
+						Math.max(0, Math.min(prev - 1, filteredCommands.length - 1))
+					);
 				} else if (e.key === 'Tab' || e.key === 'Enter') {
 					e.preventDefault();
-					const clampedIndex = Math.min(selectedSlashCommandIndex, filteredCommands.length - 1);
+					const clampedIndex = Math.max(
+						0,
+						Math.min(selectedSlashCommandIndex, filteredCommands.length - 1)
+					);
 					if (clampedIndex >= 0 && filteredCommands[clampedIndex]) {
 						setInputValue(filteredCommands[clampedIndex].command);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/renderer/hooks/input/useInputKeyDown.ts` around lines 226 - 236, The
Arrow key handlers and Enter/Tab use a potentially stale
selectedSlashCommandIndex against filteredCommands; update the handlers to
always clamp indices against the current filteredCommands.length before using or
updating them: in ArrowDown/ArrowUp update setSelectedSlashCommandIndex using a
clamp that computes min/max against filteredCommands.length - 1 and 0 (using
filteredCommands.length at call time), and before using
selectedSlashCommandIndex for Enter/Tab compute a clampedIndex =
Math.min(Math.max(selectedSlashCommandIndex, 0), filteredCommands.length - 1)
and only call setInputValue(filteredCommands[clampedIndex].command) when
clampedIndex >= 0 and filteredCommands[clampedIndex] exists; ensure all uses
reference selectedSlashCommandIndex, setSelectedSlashCommandIndex,
filteredCommands, and setInputValue so navigation and selection never operate on
out-of-range indices.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/web/mobile/SlashCommandAutocomplete.tsx`:
- Around line 96-116: The code builds a `query` from any inputValue which can
leak into highlighting even when fuzzy mode is disabled; change the query
computation inside the `useMemo` for `filteredCommands` so it is only derived
when `shouldFuzzyFilter` is true (e.g. const query = shouldFuzzyFilter ?
(inputValue || '').toLowerCase().replace(/^\//, '') : '';), keep using
`shouldFuzzyFilter` in the fuzzy path that calls `fuzzyMatchWithScore`, and
ensure `shouldFuzzyFilter` remains in the hook deps so highlighting only occurs
for slash queries.

---

Outside diff comments:
In `@src/renderer/hooks/input/useInputKeyDown.ts`:
- Around line 226-236: The Arrow key handlers and Enter/Tab use a potentially
stale selectedSlashCommandIndex against filteredCommands; update the handlers to
always clamp indices against the current filteredCommands.length before using or
updating them: in ArrowDown/ArrowUp update setSelectedSlashCommandIndex using a
clamp that computes min/max against filteredCommands.length - 1 and 0 (using
filteredCommands.length at call time), and before using
selectedSlashCommandIndex for Enter/Tab compute a clampedIndex =
Math.min(Math.max(selectedSlashCommandIndex, 0), filteredCommands.length - 1)
and only call setInputValue(filteredCommands[clampedIndex].command) when
clampedIndex >= 0 and filteredCommands[clampedIndex] exists; ensure all uses
reference selectedSlashCommandIndex, setSelectedSlashCommandIndex,
filteredCommands, and setInputValue so navigation and selection never operate on
out-of-range indices.
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: a7f52246-c3ae-4bd2-8594-0ed5735a53f0

📥 Commits

Reviewing files that changed from the base of the PR and between 91024db and 76fc248.

📒 Files selected for processing (8)
  • src/__tests__/renderer/components/InputArea.test.tsx
  • src/__tests__/renderer/hooks/useInputKeyDown.test.ts
  • src/__tests__/renderer/utils/search.test.ts
  • src/__tests__/web/mobile/SlashCommandAutocomplete.test.tsx
  • src/renderer/components/InputArea.tsx
  • src/renderer/hooks/input/useInputKeyDown.ts
  • src/renderer/utils/search.ts
  • src/web/mobile/SlashCommandAutocomplete.tsx

Copy link
Copy Markdown

@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: 2

🧹 Nitpick comments (1)
src/__tests__/web/mobile/SlashCommandAutocomplete.test.tsx (1)

277-278: Avoid hard-coding the last AI-mode index.

1 is only correct while AI mode exposes exactly two default commands. Adding another AI-visible command will fail this test even if clamping still behaves correctly.

💡 Suggested refactor
-			// Effect should clamp to last valid index
-			expect(onSelectedIndexChange).toHaveBeenCalledWith(1);
+			// Effect should clamp to the last valid AI-visible index
+			const lastAiIndex =
+				DEFAULT_SLASH_COMMANDS.filter((cmd) => !cmd.terminalOnly).length - 1;
+			expect(onSelectedIndexChange).toHaveBeenCalledWith(lastAiIndex);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/__tests__/web/mobile/SlashCommandAutocomplete.test.tsx` around lines 277
- 278, The test currently hard-codes the last AI-mode index as 1 when asserting
onSelectedIndexChange; change the assertion to compute the expected last index
dynamically (e.g., derive it from the AI-visible commands or the rendered
options count) instead of using the literal 1 so the test stays correct if more
AI-visible commands are added; locate the assertion around onSelectedIndexChange
in SlashCommandAutocomplete.test.tsx and replace the hard-coded 1 with a
computed value such as (aiModeCommands.length - 1) or (renderedOptions.length -
1) depending on how the test constructs the command list.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/renderer/hooks/input/useInputKeyDown.ts`:
- Around line 220-222: selectedSlashCommandIndex can be -1 and Math.min
preserves that, causing Enter/Tab to no-op even after items reappear; update the
clamp so it bounds on both sides (e.g. compute clampedIndex = Math.max(0,
Math.min(selectedSlashCommandIndex, filteredCommands.length - 1))) and/or
early-return when filteredCommands.length === 0, then call
setInputValue(filteredCommands[clampedIndex].command) only when
filteredCommands[clampedIndex] exists; reference symbols:
selectedSlashCommandIndex, filteredCommands, clampedIndex, setInputValue.

In `@src/renderer/utils/search.ts`:
- Around line 166-179: The highlighting loop currently prefers a later
boundaryMatch over an earlier firstMatch, causing visuals to disagree with
fuzzyMatchWithScore; update the selection logic in this block (using
variables/funcs lowerText, lowerQuery, isBoundary, canMatchRest, firstMatch,
boundaryMatch, indices) so it reuses the scorer's tie-breaking: prefer
firstMatch when it yields a consecutive/prefix continuation (e.g., j ===
previousChosen + 1 or matches the same "prefix" condition the scorer uses),
otherwise fall back to boundaryMatch; ideally accept a path or flags from
fuzzyMatchWithScore or replicate its prefix/consecutive tie-break rules here so
highlight indices match the scoring path.

---

Nitpick comments:
In `@src/__tests__/web/mobile/SlashCommandAutocomplete.test.tsx`:
- Around line 277-278: The test currently hard-codes the last AI-mode index as 1
when asserting onSelectedIndexChange; change the assertion to compute the
expected last index dynamically (e.g., derive it from the AI-visible commands or
the rendered options count) instead of using the literal 1 so the test stays
correct if more AI-visible commands are added; locate the assertion around
onSelectedIndexChange in SlashCommandAutocomplete.test.tsx and replace the
hard-coded 1 with a computed value such as (aiModeCommands.length - 1) or
(renderedOptions.length - 1) depending on how the test constructs the command
list.
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 2e4e068c-9045-4c3b-823b-2060c3ea40c8

📥 Commits

Reviewing files that changed from the base of the PR and between 76fc248 and 8d06f87.

📒 Files selected for processing (8)
  • src/__tests__/renderer/components/InputArea.test.tsx
  • src/__tests__/renderer/hooks/useInputKeyDown.test.ts
  • src/__tests__/renderer/utils/search.test.ts
  • src/__tests__/web/mobile/SlashCommandAutocomplete.test.tsx
  • src/renderer/components/InputArea.tsx
  • src/renderer/hooks/input/useInputKeyDown.ts
  • src/renderer/utils/search.ts
  • src/web/mobile/SlashCommandAutocomplete.tsx
🚧 Files skipped from review as they are similar to previous changes (4)
  • src/web/mobile/SlashCommandAutocomplete.tsx
  • src/tests/renderer/hooks/useInputKeyDown.test.ts
  • src/tests/renderer/components/InputArea.test.tsx
  • src/tests/renderer/utils/search.test.ts

- Extract shared filterSlashCommands() and highlightSlashCommand() utilities
  to eliminate duplicated filter/sort pipeline across InputArea, useInputKeyDown,
  and mobile SlashCommandAutocomplete
- Make '.' boundary scoring opt-in via extraBoundaryChars param to avoid
  affecting shared fuzzyMatchWithScore callers (file search, tab switcher, etc.)
- Add suffix-safety lookahead to fuzzyMatchWithIndices so boundary-preferred
  positions are only chosen when remaining query chars can still match
- Pre-compute scores once per command (map→filter→sort) instead of calling
  fuzzyMatchWithScore twice per comparison in sort
- Clamp selectedSlashCommandIndex before Enter/Tab acceptance on desktop
- Clamp to last valid index (not 0) on mobile when filtered list shrinks
- Align mobile sort guard with filter guard (shouldFuzzyFilter inside useMemo)
- Remove redundant shouldFuzzyFilter from useMemo deps
- Fix tests for fuzzy matching (function matchers for split-span text,
  updated query assumptions)
Copy link
Copy Markdown

@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.

🧹 Nitpick comments (1)
src/renderer/components/InputArea.tsx (1)

525-527: Consider memoizing the query string for highlighting.

The query inputValueLower.replace(/^\//, '') is recomputed for each command in the list. While the overhead is minimal for typical command lists, memoizing it would align with the PERF patterns elsewhere in this file.

♻️ Suggested change

Extract the query outside the useMemo scope so both filtering and highlighting share it:

 const inputValueLower = useMemo(() => inputValue.toLowerCase(), [inputValue]);
+const slashQuery = useMemo(() => inputValueLower.replace(/^\//, ''), [inputValueLower]);
 const filteredSlashCommands = useMemo(() => {
-	const query = inputValueLower.replace(/^\//, '');
-	return filterSlashCommands(slashCommands, query, isTerminalMode);
-}, [slashCommands, isTerminalMode, inputValueLower]);
+	return filterSlashCommands(slashCommands, slashQuery, isTerminalMode);
+}, [slashCommands, isTerminalMode, slashQuery]);

Then in the render:

 <div className="font-mono text-sm">
-	{highlightSlashCommand(cmd.command, inputValueLower.replace(/^\//, ''))}
+	{highlightSlashCommand(cmd.command, slashQuery)}
 </div>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/renderer/components/InputArea.tsx` around lines 525 - 527, The code
repeatedly calls inputValueLower.replace(/^\//, '') for each command when
rendering highlightSlashCommand; compute this cleaned query once (e.g., const
slashQuery = inputValueLower.replace(/^\//, '') outside the commands
useMemo/render loop) and reuse slashQuery in both the filtering logic and the
highlightSlashCommand call to avoid redundant work and align with existing PERF
patterns; update references to use slashQuery and keep the existing
highlightSlashCommand and inputValueLower identifiers.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/renderer/components/InputArea.tsx`:
- Around line 525-527: The code repeatedly calls inputValueLower.replace(/^\//,
'') for each command when rendering highlightSlashCommand; compute this cleaned
query once (e.g., const slashQuery = inputValueLower.replace(/^\//, '') outside
the commands useMemo/render loop) and reuse slashQuery in both the filtering
logic and the highlightSlashCommand call to avoid redundant work and align with
existing PERF patterns; update references to use slashQuery and keep the
existing highlightSlashCommand and inputValueLower identifiers.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: bb02f8d9-d8ab-4d1b-8a38-1150443b210c

📥 Commits

Reviewing files that changed from the base of the PR and between 8d06f87 and 179e8a4.

📒 Files selected for processing (8)
  • src/__tests__/renderer/components/InputArea.test.tsx
  • src/__tests__/renderer/hooks/useInputKeyDown.test.ts
  • src/__tests__/renderer/utils/search.test.ts
  • src/__tests__/web/mobile/SlashCommandAutocomplete.test.tsx
  • src/renderer/components/InputArea.tsx
  • src/renderer/hooks/input/useInputKeyDown.ts
  • src/renderer/utils/search.ts
  • src/web/mobile/SlashCommandAutocomplete.tsx
🚧 Files skipped from review as they are similar to previous changes (6)
  • src/tests/renderer/hooks/useInputKeyDown.test.ts
  • src/tests/renderer/components/InputArea.test.tsx
  • src/tests/web/mobile/SlashCommandAutocomplete.test.tsx
  • src/tests/renderer/utils/search.test.ts
  • src/web/mobile/SlashCommandAutocomplete.tsx
  • src/renderer/utils/search.ts

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.

2 participants