apps/web: add in-thread find/search for chat conversations#1501
apps/web: add in-thread find/search for chat conversations#1501leonardoxr wants to merge 11 commits into
Conversation
|
Important Review skippedAuto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
Tip 💬 Introducing Slack Agent: The best way for teams to turn conversations into code.Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.
Built for teams:
One agent for your entire SDLC. Right inside Slack. 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. Comment |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 72d67ca4fb
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 7828e85e7f
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 2fd077b092
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 340789c4b9
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 776dfcfe8d
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 50affe963c
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: a652fd5ee6
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: f9bfd72303
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: cd11e1f59d
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: e8e4642e0b
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
I haven't taken the time to review any changes yet, but I wanted to bring attention to this PR. This is a really important and useful feature that I find myself reaching for all the time. |
|
This feature would be great to have |
yeah, if my implementation is ok, i'd update it to make it up to date and ready for any requested changes |
e8e4642 to
633d906
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 633d906aab
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| if (!isThreadSearchInputTarget(activeElement)) { | ||
| threadSearchRestoreFocusRef.current = activeElement; |
There was a problem hiding this comment.
Preserve focus when find is opened from editable inputs
openThreadSearch only remembers the previously focused element when it is not an input/textarea/select/contentEditable node. If a user triggers Cmd/Ctrl+F while typing in the composer, closeThreadSearch has no saved focus target and focus falls back to the document after closing, so keyboard typing cannot resume immediately. This breaks the intended focus-restore behavior for a common workflow.
Useful? React with 👍 / 👎.
ApprovabilityVerdict: Needs human review 1 blocking correctness issue found. This PR introduces a substantial new feature (in-thread find/search) with new components, search logic, and keyboard shortcuts. New user-facing features warrant human review, and multiple unresolved comments identify correctness issues including list re-rendering bugs and rendering regressions. You can customize Macroscope's approvability policy. Learn more. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: efbd61f2e0
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| visibleMessageState?.visibleText ?? "", | ||
| visibleMessageState?.contexts ?? [], | ||
| ) | ||
| : markdownToPlainText(visibleAssistantText), |
There was a problem hiding this comment.
Index rendered file-link labels for assistant markdown search
Build the assistant search text from the same file-link labels that ChatMarkdown renders, not from raw markdown alone. collectRowSearchText currently indexes markdownToPlainText(visibleAssistantText), but assistant links that resolve to local files are rendered as MarkdownFileLink labels derived from path metadata (basename/parent/line), not the original markdown link text. In threads containing [custom label](./path/to/file.ts), searching the original label can produce an active row with no visible <mark>, while searching the visible file-link label can miss entirely.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
💡 Codex Review
When a markdown link is rendered as MarkdownFileLink, the visible label text is produced from path metadata (e.g. file.ts · src/app · L12), but this label is rendered as plain text with no search highlighting. Since thread indexing now includes these rendered labels, queries that match only this visible label can make the row active without any inline <mark>, so users still cannot localize the hit. This is reproducible with links like [custom text](./src/file.ts) where the rendered label differs from the original markdown text.
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| markdownToPlainText(visibleAssistantText), | ||
| ...collectMarkdownFileLinkLabels(visibleAssistantText, options?.markdownCwd), | ||
| ] |
There was a problem hiding this comment.
Index rendered skill-chip labels for searchable message text
Message-row indexing uses raw text (markdownToPlainText(...) for assistant content and buildRenderedUserMessageText(...) for user content) but does not add the human-readable skill chip labels rendered by SkillInlineText/renderSkillInlineMarkdownChildren. In threads containing known skill tokens (for example $web_search rendered as Web Search), searching the visible chip label text will miss the row entirely because only token-form text is indexed.
Useful? React with 👍 / 👎.
| </div> | ||
| )} | ||
| {/* Messages — LegendList handles virtualization and scrolling internally */} | ||
| <MessagesTimeline |
There was a problem hiding this comment.
🟠 High components/ChatView.tsx:3761
The renderItem callback in MessagesTimeline depends on search state (activeSearchRowId, matchedSearchRowIds, searchQuery), but LegendList only re-renders items when itemKey, data, or extraData changes. Since no extraData prop is passed, items already on screen never re-render when search state updates — visible rows don't gain/lose search highlights as the user types or navigates results.
🤖 Copy this AI Prompt to have your agent fix this:
In file apps/web/src/components/ChatView.tsx around line 3761:
The `renderItem` callback in `MessagesTimeline` depends on search state (`activeSearchRowId`, `matchedSearchRowIds`, `searchQuery`), but `LegendList` only re-renders items when `itemKey`, `data`, or `extraData` changes. Since no `extraData` prop is passed, items already on screen never re-render when search state updates — visible rows don't gain/lose search highlights as the user types or navigates results.
Evidence trail:
- apps/web/src/components/chat/MessagesTimeline.tsx lines 270-291: `renderItem` useCallback with deps `[activeSearchRowId, matchedSearchRowIds, searchQuery]`
- apps/web/src/components/chat/MessagesTimeline.tsx lines 307-324: `<LegendList>` with no `extraData` prop
- LegendApp/legend-list src/components/Container.tsx line 165: `useMemo(() => getRenderedItem(itemKey), [itemKey, data, extraData])` — `getRenderedItem` not in deps
- LegendApp/legend-list src/utils/getRenderedItem.ts: reads `renderItem` from `state.props` and calls it, but the memo in Container only reacts to `itemKey`, `data`, `extraData`
- apps/web/package.json line 23: `@legendapp/list: 3.0.0-beta.44`
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 0db8951448
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| const normalizedText = text.toLocaleLowerCase(); | ||
| const ranges: TextMatchRange[] = []; | ||
| let searchStart = 0; | ||
|
|
||
| while (searchStart <= normalizedText.length - normalizedQuery.length) { | ||
| const matchIndex = normalizedText.indexOf(normalizedQuery, searchStart); |
There was a problem hiding this comment.
Compute highlight ranges against original string indices
findMatchRanges lowercases the full text before searching, then reuses those indices to slice the original string during rendering. Locale-aware lowercasing can change string length (for example İ becomes i̇), so any match after such characters gets shifted and the <mark> can wrap the wrong substring (or an empty one). In those cases search navigation reports a hit but the visible inline highlight is misplaced/non-localizable in internationalized content.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 0db8951. Configure here.
| return <SkillInlineText text={text} skills={skills} />; | ||
| } | ||
| return renderHighlightedText(text, searchQuery, keyPrefix, { active: searchActive }); | ||
| } |
There was a problem hiding this comment.
Search highlighting drops skill chip rendering in user messages
Medium Severity
renderSearchableUserText switches from SkillInlineText (which renders skill tokens as styled chips) to plain renderHighlightedText whenever searchQuery is non-empty. This causes ALL matched user-message rows containing skill tokens (e.g. $github:gh-fix-ci) to display the raw token text instead of the rendered chip UI, regardless of whether the search query targets a skill. Unlike terminal-context chips, which preserve their chip rendering during search, skills regress to unreadable raw tokens.
Reviewed by Cursor Bugbot for commit 0db8951. Configure here.


Summary
Adds a Codex-style in-thread search flow to the chat view in
apps/web.Cmd+F/Ctrl+FEnter,Shift+Enter, and the next/previous buttonsWhy
Closes #1486.
The issue asks for a
Cmd+F/Ctrl+Fstyle find-in-thread flow because it is hard to locate earlier content in long conversations by scrolling, especially during active generation.Scope
This PR is intentionally limited to the web thread-search experience.
It does not include unrelated server/runtime compatibility changes or local dev seeding helpers.
Before / After
Before:
After:
Validation
bun fmtbun lintbun typecheckbun run test src/components/chat/threadSearch.test.tsbun run test src/components/ChatMarkdown.test.tsx src/components/chat/MessagesTimeline.test.tsx src/components/chat/threadSearchHighlight.test.tsx src/components/chat/ProposedPlanCard.test.tsxbun run test:browser src/components/ChatView.threadSearch.browser.tsxNotes
This is still a fairly broad UI change for a single feature, but all included edits are directly tied to making thread search usable across the actual thread surfaces that can render searchable content.
Note
Medium Risk
Adds new interactive search state and indexing/highlighting logic across virtualized chat timeline rendering; main risk is regressions in message/work/plan rendering and code-block highlighting behavior under search.
Overview
Adds an in-thread find UI to
ChatView(Cmd/Ctrl+F) with an overlaidThreadSearchBar, incremental query lookup, next/previous navigation, focus restore, and auto-scrolling the virtualized timeline to the active match.Implements
threadSearchindexing over timeline rows (assistant markdown/plain text, user-visible message text, work log entries, proposed plans) and propagates match state intoMessagesTimelineto highlight matches inline and temporarily expand otherwise-collapsed content while searching.Updates
ChatMarkdown,ProposedPlanCard, terminal context chips, and markdown link helpers to support search highlighting (including a rehype-based<mark>wrapper), plus addsmarkdownToPlainTextand extensive unit/browser tests; introducesdecode-named-character-referenceas a dependency.Reviewed by Cursor Bugbot for commit 0db8951. Bugbot is set up for automated code reviews on this repo. Configure here.
Note
Add in-thread find/search for chat conversations with Cmd/Ctrl+F shortcut
ThreadSearchBarUI overlay that opens via Cmd/Ctrl+F, supporting keyboard navigation (Enter/Shift+Enter to step through matches, Escape to close) and a results counter.buildThreadSearchIndex) over all timeline row types (user messages, assistant markdown, work logs, proposed plans), normalizing text to lowercased plain text for matching.findThreadSearchLookupState, which reuses previous candidate sets when the query extends rather than rescanning the full index.renderHighlightedText(React nodes) and a rehype plugin (createThreadSearchHighlightRehypePlugin) for markdown content; code blocks with matches render plain<mark>wraps instead of syntax-highlighted output.markdownToPlainTextfor converting markdown to searchable plain text, with HTML entity decoding via the newdecode-named-character-referencedependency.Macroscope summarized 0db8951.