Skip to content

Mirror: Better search UX (#5726)#20

Merged
jeremylongshore merged 2 commits intomainfrom
review/PR-5726
Feb 15, 2026
Merged

Mirror: Better search UX (#5726)#20
jeremylongshore merged 2 commits intomainfrom
review/PR-5726

Conversation

@jeremylongshore
Copy link
Copy Markdown
Owner

Mirror of Kilo-Org#5726 for multi-AI review analysis.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Feb 14, 2026

Warning

Rate limit exceeded

@jeremylongshore has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 11 minutes and 47 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch review/PR-5726

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.

@gemini-code-assist
Copy link
Copy Markdown

Summary of Changes

Hello @jeremylongshore, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly improves the search user experience by introducing visual highlighting of matched characters within search results. By integrating a new HighlightedText component with an enhanced fuzzy search algorithm that tracks character positions, users can now quickly discern why a particular item appeared in their search, leading to more intuitive and efficient navigation.

Highlights

  • Enhanced Search User Experience: Implemented character highlighting in search results for 'Recommended models' and general model search, making it easier for users to identify matched terms.
  • New HighlightedText Component: Introduced a reusable React component, HighlightedText, responsible for rendering text and visually emphasizing specific character positions.
  • Fuzzy Search Integration: Modified the existing fuzzy search utility (Fzf) to not only find matches but also return the precise character positions of those matches, enabling the new highlighting feature.
  • Select Dropdown Updates: Updated the SelectDropdown component to consume the matching positions from the fuzzy search and pass them to the HighlightedText component for rendering.
Changelog
  • .changeset/search-highlight.md
    • Added a new changeset file documenting the search highlighting feature.
  • webview-ui/src/components/ui/highlighted-text.tsx
    • Introduced a new HighlightedText React component to visually emphasize matching characters in a string.
  • webview-ui/src/components/ui/select-dropdown.tsx
    • Imported the new HighlightedText component.
    • Extended DropdownOption interface to include matchingPositions.
    • Modified fuzzy search logic to capture and store matching character positions.
    • Rendered dropdown option labels using HighlightedText to display matches.
  • webview-ui/src/lib/word-boundary-fzf.ts
    • Modified FzfResult interface to include positions (a Set of numbers).
    • Updated find method to return empty Set for no-query results.
    • Refactored matchAcronym to return Set<number> representing matched character indices instead of a boolean.
    • Introduced a tokenize helper method to break text into words with their original indices.
Activity
  • No human activity has been recorded on this pull request yet.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@qodo-code-review
Copy link
Copy Markdown

Review Summary by Qodo

Add search result highlighting with character position tracking

✨ Enhancement

Grey Divider

Walkthroughs

Description
• Add character position tracking to fuzzy search results
• Implement visual highlighting of matched characters in search results
• Create new HighlightedText component for rendering matched positions
• Integrate highlighting into SelectDropdown model search UI
Diagram
flowchart LR
  A["Fzf Search Algorithm"] -->|"Returns positions"| B["FzfResult with positions"]
  B -->|"Position data"| C["SelectDropdown Filter"]
  C -->|"Positions + Label"| D["HighlightedText Component"]
  D -->|"Renders"| E["Highlighted Search Results"]
Loading

Grey Divider

File Changes

1. webview-ui/src/lib/word-boundary-fzf.ts ✨ Enhancement +52/-24

Track character positions in fuzzy search matches

• Add positions: Set<number> field to FzfResult interface to track matched character positions
• Modify matchAcronym method to return Set<number> | null instead of boolean, tracking exact
 positions of matched characters
• Add new tokenize method to convert text into tokens with their original string indices
• Update find method to populate positions in results and pass tokens to matching logic
• Make find method public for external access

webview-ui/src/lib/word-boundary-fzf.ts


2. webview-ui/src/components/ui/highlighted-text.tsx ✨ Enhancement +60/-0

New component for rendering highlighted text

• Create new React component to render text with highlighted matching positions
• Accept text, matchingPositions Set, and optional className props
• Iterate through text and group consecutive matched/unmatched characters
• Apply bold styling and text-vscode-textLink-foreground color to matched positions
• Memoize component for performance optimization

webview-ui/src/components/ui/highlighted-text.tsx


3. webview-ui/src/components/ui/select-dropdown.tsx ✨ Enhancement +24/-12

Integrate highlighting into dropdown search results

• Import new HighlightedText component
• Add matchingPositions?: Set<number> field to DropdownOption interface
• Update filteredOptions logic to extract and map matching positions from fuzzy search results
• Clone options with matching positions data without mutating originals
• Replace plain text rendering with HighlightedText component in dropdown items
• Remove disableSearch from dependency array as it's no longer used in filter logic

webview-ui/src/components/ui/select-dropdown.tsx


View more (1)
4. .changeset/search-highlight.md 📝 Documentation +5/-0

Add changeset for search highlighting feature

• Add changeset entry documenting the search highlighting feature
• Mark as patch version bump for kilo-code package
• Credit contributor @bernaferrari for the feature

.changeset/search-highlight.md


Grey Divider

Qodo Logo

@github-actions
Copy link
Copy Markdown

Failed to generate code suggestions for PR

Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request improves the search user experience by highlighting matched characters in search results within dropdowns. This is achieved by introducing a new HighlightedText component and modifying the Fzf search implementation to return the positions of matched characters. The changes are well-structured. I've suggested a small refactoring in the new HighlightedText component to simplify its logic and improve maintainability.

Comment on lines +20 to +55
for (let i = 0; i < text.length; i++) {
const isMatch = matchingPositions.has(i)
const isLastMatch = i > 0 && matchingPositions.has(i - 1)

if (isMatch !== isLastMatch) {
// specific transition, push previous chunk
if (i > lastIndex) {
const chunk = text.slice(lastIndex, i)
parts.push(
isLastMatch ? (
<span key={lastIndex} className="font-bold text-vscode-textLink-foreground">
{chunk}
</span>
) : (
<span key={lastIndex}>{chunk}</span>
),
)
}
lastIndex = i
}
}

// Push trailing chunk
if (lastIndex < text.length) {
const chunk = text.slice(lastIndex)
const isMatch = matchingPositions.has(text.length - 1)
parts.push(
isMatch ? (
<span key={lastIndex} className="font-bold text-vscode-textLink-foreground">
{chunk}
</span>
) : (
<span key={lastIndex}>{chunk}</span>
),
)
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

The logic for splitting the text into highlighted and non-highlighted parts can be simplified. The current implementation uses a for loop and then has a separate block of code to handle the 'trailing chunk'. This can be consolidated into a single loop that iterates up to text.length (inclusive). This change eliminates code duplication and makes the component easier to maintain without changing its behavior.

	for (let i = 0; i <= text.length; i++) {
		const isMatch = matchingPositions.has(i)
		const isLastMatch = i > 0 && matchingPositions.has(i - 1)

		if (isMatch !== isLastMatch) {
			if (i > lastIndex) {
				const chunk = text.slice(lastIndex, i)
				parts.push(
					isLastMatch ? (
						<span key={lastIndex} className="font-bold text-vscode-textLink-foreground">
							{chunk}
						</span>
					) : (
						<span key={lastIndex}>{chunk}</span>
					),
				)
			}
			lastIndex = i
		}
	}

@qodo-code-review
Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (3) 📘 Rule violations (3) 📎 Requirement gaps (0)

Grey Divider


Action required

1. HighlightedText missing kilocode_change header 📘 Rule violation ⛯ Reliability
Description
A new file was added under webview-ui/ without the required // kilocode_change - new file
marker, increasing the risk of upstream sync/merge conflicts. This violates the requirement to
annotate upstream-shared code changes.
Code

webview-ui/src/components/ui/highlighted-text.tsx[R1-3]

+import React, { memo } from "react"
+import { cn } from "@/lib/utils"
+
Evidence
Compliance ID 7 requires kilocode_change markers for new files in upstream-shared directories like
webview-ui/. The new file begins immediately with imports and contains no `// kilocode_change -
new file` header.

AGENTS.md
webview-ui/src/components/ui/highlighted-text.tsx[1-3]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
A new file was added in an upstream-shared path (`webview-ui/`) without the required `// kilocode_change - new file` annotation.

## Issue Context
This repo requires Kilo-specific modifications in upstream-shared code to be clearly marked to minimize merge conflicts during upstream syncs.

## Fix Focus Areas
- webview-ui/src/components/ui/highlighted-text.tsx[1-3]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. Unmarked filteredOptions logic change 📘 Rule violation ⛯ Reliability
Description
The new fuzzy-match/position mapping logic in select-dropdown.tsx is not wrapped/annotated with
kilocode_change markers even though it modifies upstream-shared webview-ui/ code. This can cause
ambiguity and merge conflicts when syncing from upstream.
Code

webview-ui/src/components/ui/select-dropdown.tsx[R139-163]

+				// Get fuzzy matching items AND their positions
+				const matchingResults = fzfInstance.find(searchValue)
+				const matchMap = new Map(matchingResults.map((r) => [r.item.original.value, r.positions]))

-				// Get fuzzy matching items - only perform search if we have a search value
-				const matchingItems = fzfInstance.find(searchValue).map((result) => result.item.original)
-
-				// Always include separators, shortcuts, and labels
-				return options.filter((option) => {
+				// Always include separators, shortcuts, and labels, and matched items
+				return options.reduce((acc, option) => {
					if (
						option.type === DropdownOptionType.SEPARATOR ||
						option.type === DropdownOptionType.SHORTCUT ||
						option.type === DropdownOptionType.LABEL // kilocode_change: include LABEL in filtered results
					) {
-						return true
+						acc.push(option)
+						return acc
					}

					// Include if it's in the matching items
-					return matchingItems.some((item) => item.value === option.value)
-				})
-			}, [options, searchValue, fzfInstance, disableSearch])
+					const positions = matchMap.get(option.value)
+					if (positions) {
+						// Clone option to add matching positions without mutating original
+						acc.push({ ...option, matchingPositions: positions })
+					}
+
+					return acc
+				}, [] as DropdownOption[])
+			}, [options, searchValue, fzfInstance])
Evidence
Compliance ID 7 requires changes in upstream-shared webview-ui/ files to be marked with
kilocode_change annotations. The new matchingResults/matchMap reduce-based filtering block is
a substantive change but is not enclosed in a // kilocode_change start ... `// kilocode_change
end` marker (nor are all modified lines individually marked).

AGENTS.md
webview-ui/src/components/ui/select-dropdown.tsx[137-163]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`webview-ui/src/components/ui/select-dropdown.tsx` contains a substantive logic change that is not clearly annotated with `kilocode_change` markers as required for upstream-shared code.

## Issue Context
This repository uses `kilocode_change` markers to reduce merge conflicts during upstream sync.

## Fix Focus Areas
- webview-ui/src/components/ui/select-dropdown.tsx[137-163]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. Highlight indices mismatch 🐞 Bug ✓ Correctness
Description
Match positions are computed against searchStr (label+value) but later used to highlight only
option.label, so queries that match only the value (or later parts of searchStr) can produce
no/incorrect highlighting.
Code

webview-ui/src/components/ui/select-dropdown.tsx[R139-162]

+				// Get fuzzy matching items AND their positions
+				const matchingResults = fzfInstance.find(searchValue)
+				const matchMap = new Map(matchingResults.map((r) => [r.item.original.value, r.positions]))

-				// Get fuzzy matching items - only perform search if we have a search value
-				const matchingItems = fzfInstance.find(searchValue).map((result) => result.item.original)
-
-				// Always include separators, shortcuts, and labels
-				return options.filter((option) => {
+				// Always include separators, shortcuts, and labels, and matched items
+				return options.reduce((acc, option) => {
					if (
						option.type === DropdownOptionType.SEPARATOR ||
						option.type === DropdownOptionType.SHORTCUT ||
						option.type === DropdownOptionType.LABEL // kilocode_change: include LABEL in filtered results
					) {
-						return true
+						acc.push(option)
+						return acc
					}

					// Include if it's in the matching items
-					return matchingItems.some((item) => item.value === option.value)
-				})
-			}, [options, searchValue, fzfInstance, disableSearch])
+					const positions = matchMap.get(option.value)
+					if (positions) {
+						// Clone option to add matching positions without mutating original
+						acc.push({ ...option, matchingPositions: positions })
+					}
+
+					return acc
+				}, [] as DropdownOption[])
Evidence
Fzf is configured to search item.searchStr, which is constructed as `[option.label,
option.value].join(" "). The returned positions` are indices into that selector text, but the UI
passes those same indices to HighlightedText along with option.label only, so indices that fall
in the value portion are out of range for the label and won’t highlight as intended.

webview-ui/src/components/ui/select-dropdown.tsx[116-135]
webview-ui/src/components/ui/select-dropdown.tsx[138-163]
webview-ui/src/components/ui/select-dropdown.tsx[383-389]
webview-ui/src/lib/word-boundary-fzf.ts[67-70]
webview-ui/src/lib/word-boundary-fzf.ts[144-150]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`SelectDropdown` searches a combined string (`label + &quot; &quot; + value`) but uses the returned character positions to highlight `option.label` only. If a query matches only the `value` portion (or produces positions outside the label prefix), highlighting will be missing/incorrect.

## Issue Context
- `searchStr` is built from `[option.label, option.value].join(&quot; &quot;)` and used as the `Fzf` selector.
- `Fzf` returns `positions` as indices into the selector text.
- UI highlights only `option.label`.

## Fix Focus Areas
- webview-ui/src/components/ui/select-dropdown.tsx[116-163]
- webview-ui/src/components/ui/select-dropdown.tsx[383-389]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

4. Highlighting lacks test coverage 📘 Rule violation ⛯ Reliability
Description
New user-facing highlighting behavior was introduced but no corresponding Vitest assertions were
added to cover the new matching-position rendering. This increases the risk of regressions in search
UX behavior.
Code

webview-ui/src/components/ui/select-dropdown.tsx[R384-389]

+																	<div className="flex items-center">
+																		<HighlightedText
+																			text={option.label}
+																			matchingPositions={option.matchingPositions}
+																		/>
+																	</div>
Evidence
Compliance ID 8 requires non-trivial behavior changes to be covered by new or updated Vitest tests.
The PR adds highlighted rendering via HighlightedText, while existing SelectDropdown tests only
assert basic text content and do not validate highlighted spans/classes.

AGENTS.md
webview-ui/src/components/ui/select-dropdown.tsx[384-389]
webview-ui/src/components/ui/highlighted-text.tsx[10-13]
webview-ui/src/components/ui/tests/select-dropdown.spec.tsx[89-96]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
New highlighting behavior (matching character rendering) was added, but tests do not assert the new UI output.

## Issue Context
The dropdown now passes `matchingPositions` and renders `HighlightedText`, which should be covered by Vitest to prevent regressions in the search UX.

## Fix Focus Areas
- webview-ui/src/components/ui/select-dropdown.tsx[384-389]
- webview-ui/src/components/ui/highlighted-text.tsx[10-57]
- webview-ui/src/components/ui/__tests__/select-dropdown.spec.tsx[89-120]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


5. No-match lacks truncate 🐞 Bug ✓ Correctness
Description
HighlightedText omits the truncate class when there are no matches (undefined/empty positions),
leading to inconsistent wrapping/overflow behavior compared to the highlighted path.
Code

webview-ui/src/components/ui/highlighted-text.tsx[R10-13]

+export const HighlightedText = memo(({ text, matchingPositions, className }: HighlightedTextProps) => {
+	if (!matchingPositions || matchingPositions.size === 0) {
+		return <span className={className}>{text}</span>
+	}
Evidence
The component returns a plain <span className={className}> when there are no matches, but returns
<span className={cn("truncate", className)}> when there are matches. This makes layout dependent
on whether highlighting is active, which is likely unintended given the wrapper explicitly adds
truncate.

webview-ui/src/components/ui/highlighted-text.tsx[10-13]
webview-ui/src/components/ui/highlighted-text.tsx[57-57]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`HighlightedText` only applies the `truncate` class when there are highlighted parts. When there are no matches, it returns a span without `truncate`, which can cause wrapping/overflow differences.

## Issue Context
The component likely intends to be a drop-in replacement for plain text with consistent truncation.

## Fix Focus Areas
- webview-ui/src/components/ui/highlighted-text.tsx[10-13]
- webview-ui/src/components/ui/highlighted-text.tsx[57-57]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


6. disableSearch still filters 🐞 Bug ⛯ Reliability
Description
When disableSearch=true the input is hidden, but filtering still runs based on internal
searchValue; if disableSearch toggles while searchValue is non-empty, users can end up with a
filtered list and no way to change the query.
Code

webview-ui/src/components/ui/select-dropdown.tsx[R138-163]

			const filteredOptions = React.useMemo(() => {
-				// If search is disabled or no search value, return all options without filtering
-				if (disableSearch || !searchValue) return options
+				// Get fuzzy matching items AND their positions
+				const matchingResults = fzfInstance.find(searchValue)
+				const matchMap = new Map(matchingResults.map((r) => [r.item.original.value, r.positions]))

-				// Get fuzzy matching items - only perform search if we have a search value
-				const matchingItems = fzfInstance.find(searchValue).map((result) => result.item.original)
-
-				// Always include separators, shortcuts, and labels
-				return options.filter((option) => {
+				// Always include separators, shortcuts, and labels, and matched items
+				return options.reduce((acc, option) => {
					if (
						option.type === DropdownOptionType.SEPARATOR ||
						option.type === DropdownOptionType.SHORTCUT ||
						option.type === DropdownOptionType.LABEL // kilocode_change: include LABEL in filtered results
					) {
-						return true
+						acc.push(option)
+						return acc
					}

					// Include if it's in the matching items
-					return matchingItems.some((item) => item.value === option.value)
-				})
-			}, [options, searchValue, fzfInstance, disableSearch])
+					const positions = matchMap.get(option.value)
+					if (positions) {
+						// Clone option to add matching positions without mutating original
+						acc.push({ ...option, matchingPositions: positions })
+					}
+
+					return acc
+				}, [] as DropdownOption[])
+			}, [options, searchValue, fzfInstance])
Evidence
disableSearch only gates rendering the search input, while filteredOptions is always derived
from fzfInstance.find(searchValue) and doesn’t consider disableSearch. This becomes user-visible
if the prop changes while the component is open and searchValue is already set (state is internal
and persists).

webview-ui/src/components/ui/select-dropdown.tsx[285-304]
webview-ui/src/components/ui/select-dropdown.tsx[137-163]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`disableSearch` currently hides the search UI but does not prevent filtering. If `disableSearch` becomes true while `searchValue` is non-empty (state persists), the dropdown can remain filtered without any UI to edit/clear the query.

## Issue Context
Filtering uses `fzfInstance.find(searchValue)` unconditionally.

## Fix Focus Areas
- webview-ui/src/components/ui/select-dropdown.tsx[137-163]
- webview-ui/src/components/ui/select-dropdown.tsx[270-304]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

ⓘ The new review experience is currently in Beta. Learn more

Grey Divider

Qodo Logo

Comment on lines +1 to +3
import React, { memo } from "react"
import { cn } from "@/lib/utils"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Action required

1. highlightedtext missing kilocode_change header 📘 Rule violation ⛯ Reliability

A new file was added under webview-ui/ without the required // kilocode_change - new file
marker, increasing the risk of upstream sync/merge conflicts. This violates the requirement to
annotate upstream-shared code changes.
Agent Prompt
## Issue description
A new file was added in an upstream-shared path (`webview-ui/`) without the required `// kilocode_change - new file` annotation.

## Issue Context
This repo requires Kilo-specific modifications in upstream-shared code to be clearly marked to minimize merge conflicts during upstream syncs.

## Fix Focus Areas
- webview-ui/src/components/ui/highlighted-text.tsx[1-3]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment on lines +139 to +163
// Get fuzzy matching items AND their positions
const matchingResults = fzfInstance.find(searchValue)
const matchMap = new Map(matchingResults.map((r) => [r.item.original.value, r.positions]))

// Get fuzzy matching items - only perform search if we have a search value
const matchingItems = fzfInstance.find(searchValue).map((result) => result.item.original)

// Always include separators, shortcuts, and labels
return options.filter((option) => {
// Always include separators, shortcuts, and labels, and matched items
return options.reduce((acc, option) => {
if (
option.type === DropdownOptionType.SEPARATOR ||
option.type === DropdownOptionType.SHORTCUT ||
option.type === DropdownOptionType.LABEL // kilocode_change: include LABEL in filtered results
) {
return true
acc.push(option)
return acc
}

// Include if it's in the matching items
return matchingItems.some((item) => item.value === option.value)
})
}, [options, searchValue, fzfInstance, disableSearch])
const positions = matchMap.get(option.value)
if (positions) {
// Clone option to add matching positions without mutating original
acc.push({ ...option, matchingPositions: positions })
}

return acc
}, [] as DropdownOption[])
}, [options, searchValue, fzfInstance])
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Action required

2. Unmarked filteredoptions logic change 📘 Rule violation ⛯ Reliability

The new fuzzy-match/position mapping logic in select-dropdown.tsx is not wrapped/annotated with
kilocode_change markers even though it modifies upstream-shared webview-ui/ code. This can cause
ambiguity and merge conflicts when syncing from upstream.
Agent Prompt
## Issue description
`webview-ui/src/components/ui/select-dropdown.tsx` contains a substantive logic change that is not clearly annotated with `kilocode_change` markers as required for upstream-shared code.

## Issue Context
This repository uses `kilocode_change` markers to reduce merge conflicts during upstream sync.

## Fix Focus Areas
- webview-ui/src/components/ui/select-dropdown.tsx[137-163]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment on lines +139 to +162
// Get fuzzy matching items AND their positions
const matchingResults = fzfInstance.find(searchValue)
const matchMap = new Map(matchingResults.map((r) => [r.item.original.value, r.positions]))

// Get fuzzy matching items - only perform search if we have a search value
const matchingItems = fzfInstance.find(searchValue).map((result) => result.item.original)

// Always include separators, shortcuts, and labels
return options.filter((option) => {
// Always include separators, shortcuts, and labels, and matched items
return options.reduce((acc, option) => {
if (
option.type === DropdownOptionType.SEPARATOR ||
option.type === DropdownOptionType.SHORTCUT ||
option.type === DropdownOptionType.LABEL // kilocode_change: include LABEL in filtered results
) {
return true
acc.push(option)
return acc
}

// Include if it's in the matching items
return matchingItems.some((item) => item.value === option.value)
})
}, [options, searchValue, fzfInstance, disableSearch])
const positions = matchMap.get(option.value)
if (positions) {
// Clone option to add matching positions without mutating original
acc.push({ ...option, matchingPositions: positions })
}

return acc
}, [] as DropdownOption[])
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Action required

3. Highlight indices mismatch 🐞 Bug ✓ Correctness

Match positions are computed against searchStr (label+value) but later used to highlight only
option.label, so queries that match only the value (or later parts of searchStr) can produce
no/incorrect highlighting.
Agent Prompt
## Issue description
`SelectDropdown` searches a combined string (`label + " " + value`) but uses the returned character positions to highlight `option.label` only. If a query matches only the `value` portion (or produces positions outside the label prefix), highlighting will be missing/incorrect.

## Issue Context
- `searchStr` is built from `[option.label, option.value].join(" ")` and used as the `Fzf` selector.
- `Fzf` returns `positions` as indices into the selector text.
- UI highlights only `option.label`.

## Fix Focus Areas
- webview-ui/src/components/ui/select-dropdown.tsx[116-163]
- webview-ui/src/components/ui/select-dropdown.tsx[383-389]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

@jeremylongshore jeremylongshore merged commit 38f638b into main Feb 15, 2026
16 checks passed
jeremylongshore added a commit that referenced this pull request Feb 15, 2026
Corrected review counts to reflect honest accounting: 22 substantive
reviews with verdicts (10 APPROVE, 8 COMMENT, 4 REQUEST_CHANGES),
53 scaffolded templates. Updated DASHBOARD.md with test evidence,
PROGRESS.md with 3 missing reviews (#20-22), and all 75 status.json
files with accurate phase data.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@jeremylongshore
Copy link
Copy Markdown
Owner Author

Review Summary

Aspect Assessment
Verdict APPROVE
Confidence 4/5
Blocking Issues 0
Minor Issues 2

What Changed

Adds character-level highlighting to the model search dropdown. When typing a search query, the matching characters in each result are bolded and colored (text-vscode-textLink-foreground). This involves three changes:

  1. New HighlightedText component (webview-ui/src/components/ui/highlighted-text.tsx) - Takes text and a Set<number> of matching positions, renders highlighted spans.
  2. word-boundary-fzf.ts refactored to return positions: Set<number> alongside each match result, tracking which character indices matched.
  3. select-dropdown.tsx updated to thread positions through from fzf.find() to HighlightedText.

Checklist

Check Result Notes
Correctness PASS Position tracking logic is sound; tokenize + recursive tryMatch correctly propagate indices
Conventions PASS Tailwind classes, proper kilocode_change markers, memo on new component
Changeset PASS search-highlight.md present, patch level appropriate
Tests NOTE Existing tests still pass. No new tests for position tracking, but existing coverage of match logic is thorough
i18n N/A No user-facing strings added
Types PASS matchingPositions?: Set<number> added cleanly to DropdownOption interface
Security N/A UI-only change
Scope PASS Focused, self-contained

Minor Observations

1. disableSearch guard removed from filteredOptions memo (gray)

File: select-dropdown.tsx, filteredOptions useMemo

The PR removes the if (disableSearch || !searchValue) return options guard and also removes disableSearch from the dependency array. When disableSearch is true, fzfInstance.find("") returns all items with empty position sets, which is functionally equivalent. The behavior is preserved, but the path is slightly less obvious. Not a bug.

2. No tests for position accuracy (gray)

The existing word-boundary-fzf.spec.ts tests do not assert on positions in the returned results. The tests still pass because the return shape changed from { item } to { item, positions } and existing assertions only check item. Adding a few position-accuracy tests would strengthen confidence, but this is not blocking.

CI Status

Check Result
compile PASS
test-extension (ubuntu) PASS
test-extension (windows) PASS
test-webview (ubuntu) PASS
test-webview (windows) PASS
test-cli PASS
build-cli PASS
check-translations PASS
test-jetbrains PASS

Code Snippets

The core position-tracking change in matchAcronym:

// Before: returns boolean
private matchAcronym(text: string, query: string): boolean

// After: returns Set<number> | null (positions of matching chars)
private matchAcronym(tokens: { word: string; index: number }[], query: string): Set<number> | null

The tokenize method maps each word to its original character index in the source string, enabling accurate position reporting:

private tokenize(text: string): { word: string; index: number }[] {
    const tokens: { word: string; index: number }[] = []
    let currentIndex = 0
    const words = text.split(WORD_BOUNDARY_REGEX).filter((w) => w.length > 0)
    for (const word of words) {
        const index = text.indexOf(word, currentIndex)
        if (index !== -1) {
            tokens.push({ word, index })
            currentIndex = index + word.length
        }
    }
    return tokens
}

Verdict

APPROVE. Clean implementation of a useful UX improvement. The author (bernaferrari) has a track record of UI polish contributions. The character-level position tracking is well-integrated into the existing Fzf class without breaking the API contract. All CI passes. The two observations are informational, not blocking.


@jeremylongshore
Copy link
Copy Markdown
Owner Author

Review Journal: kilocode Kilo-Org#5726

PR: #5726 |
Author: @bernaferrari | Size: 177 lines, 4 files | Confidence: 4/5

Summary

Adds fuzzy match character highlighting to the model search dropdown. Matching characters are bolded and colored as users type. Clean implementation that extends the existing Fzf class with position tracking and introduces a new HighlightedText component. APPROVE.

First Impressions

bernaferrari is a repeat contributor focused on UI polish. The PR description is honest ("this is totally subjective") and includes before/after screenshots plus a video. The diff is 141+/36- across 4 files, which is modest for a feature PR.

What I Looked At

  • webview-ui/src/components/ui/highlighted-text.tsx (new component)
  • webview-ui/src/lib/word-boundary-fzf.ts (position tracking additions)
  • webview-ui/src/lib/__tests__/word-boundary-fzf.spec.ts (existing tests)
  • webview-ui/src/components/ui/select-dropdown.tsx (integration point)
  • Changeset file
  • CI results (all pass)

Analysis

Position tracking approach

The key design decision is storing match positions as a Set<number> of character indices in the original string. This requires the Fzf class to know where each tokenized word starts in the original text, hence the new tokenize() method that maps word -> { word, index }.

The recursive tryMatch was extended to accumulate positions. When trying different matching paths (backtracking), the function clones the position set at branch points to avoid contaminating failed paths. This is correct.

HighlightedText component

The component groups consecutive matched/unmatched characters into spans for efficient rendering (avoids one span per character). It uses memo appropriately since position sets are stable references from the search. The highlight style uses font-bold text-vscode-textLink-foreground -- Tailwind classes, not inline styles, following conventions.

Dropdown integration

The filteredOptions memo was refactored from .filter() to .reduce() to attach matchingPositions to each option. The option is shallow-cloned ({ ...option, matchingPositions: positions }) to avoid mutating the original options array. This is correct.

One subtlety: the disableSearch guard was removed from filteredOptions. When disableSearch is true, fzfInstance.find("") returns all items with empty position sets, so all items display without highlighting. Functionally equivalent, but slightly less obvious.

Missing position tests

The existing 50+ test cases for the Fzf class all still pass because they only assert on item, not positions. The return type changed from { item } to { item, positions } in a backward-compatible way. No tests verify position accuracy though.

Verification

  • All CI checks pass (compile, test-extension, test-webview, test-cli, build-cli, check-translations, test-jetbrains)
  • No review comments from other reviewers
  • No i18n impact (no new user-facing strings)

Lessons Learned

When reviewing search UX changes, verify the highlight approach handles edge cases: empty queries (returns all items with empty positions -- correct), multi-word queries (positions accumulated across all words -- correct), and backtracking (position set cloned at branch points -- correct).


Review #29 of 75 | Methodology | Reviewed with Claude Code

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