Repository navigation
Find text in local Review diffs, including deleted lines, without leaving the patch #434
Description
User problem and benefit
When reviewing an agent's changes, users often know the identifier, error text, or removed configuration they want to inspect—not its file or hunk. Local Review opens individual patches and All changes, but locating a phrase in the loaded patch still requires manual scanning. Saved-file search cannot recover deleted lines; Next file cannot locate a phrase within a large patch.
Add Find in diff to the currently loaded local Review document: literal matching, occurrence count, highlighted matches and Previous/Next. Keep additions/deletions visible, without an agent turn, filesystem search or GitHub request.
Source inspiration and implementation evidence
This is source-code/documentation research, not visual inspection or a claim that the applications were run.
- T3 Code — direct inspiration: its find-in-diff implementation searches diff line data rather than mounted rows and reveals matches. Its tests cover removed/added occurrences, folded files and context lines. Borrow patch-local find, not its web renderer, dependency patch or regex controls.
- Synara — complementary navigation lesson, not evidence of text find: its diff file-jump menu keeps focused search/navigation inside the diff, distinguishes no files from no matching files, and stops search-key propagation. Its change-navigation buttons have labels, shortcut hints and disabled states. Reuse these principles, not another file navigator.
Threadlane checked against current main fdcbd567:
RightPanelView::apply_review_diff_resultguards request/checkout identity and renders the patch as a Markdown diff block.types.rsdistinguishes File/AllChanges, request revision/options and Loading/Ready/Failed.- Shared
review_diff_bodyowns scrolling/exceptional states;diff_text_viewprovides selectable text and portable syntax colors. Neither supplies find controls. - Already-pinned GPUI Kit supplies
rendered_text,set_range_highlights,reveal_range. The existing div scroll region also needsTextView::on_revealand a tracked scroll handle: a successfulreveal_rangecall alone does not prove scrolling. No new renderer or dependency upgrade is proposed.
Proposed UX and interaction states
- Entry/scope: a quiet visible
Find in difftoolbar command; Cmd+F on macOS / Ctrl+F elsewhere only while this Review document owns focus. Open/refocus an inline strip beneath its header. Search the loaded patch's displayed text, including additions, deletions, context, headers and off-screen text, excluding synthetic Markdown fences. File view searches that patch; All changes searches its combined patch. ShowCurrent diff only. Never fetch other documents. - Query/navigation: case-insensitive literal matching, counting occurrences rather than lines. Empty:
Type to find in this diff; no match:No matches in this diff; otherwiseN of M matches, Previous, Next, Close. Enter/Shift+Enter in the find input navigate and wrap. Reveal the active match without changing the user's selection or clipboard. Distinguish active/other highlights while keeping diff syntax readable. No regex, replace, history or persistent queries in v1. - Refresh/identity: keep the query on same-target refresh or Ignore whitespace changes, but immediately invalidate ranges, show
Updating diff…, and disable navigation until the accepted patch has parsed. Recompute against the rendered revision. Do not auto-scroll on background refresh; show count plusChoose Previous or Next. Explicit file/All changes changes, checkout/session changes, Back, Close or leaving Review reset/close find. Late parse/search/reveal work cannot affect another document. - Exceptional states: retain existing loading, Retry, no-changes, whitespace-empty and failure UI; a failed load is not zero matches. Binary patches search only displayed textual summaries. Debounce scans off the UI thread over immutable rendered snapshots; supersede stale queries. Cap retained matches at 10,000 with
First 10,000 matches; refine your search, not silent truncation. Never read beyond loaded patch text. - Accessibility: standard kit Input/Buttons, visible focus, accessible names, shortcut tooltips and textual status—not color alone. Escape dismisses the topmost menu first, otherwise closes find and restores document focus. Enter cannot commit, send a prompt or leak to another find field; respect IME composition. Preserve controls at narrow widths/larger fonts with semantic light/dark/custom-theme colors.
Scoped implementation plan
- Shared presentation and matching: extend
crates/threadlane-ui-kit/src/{review.rs,diff.rs}(focused sibling module if needed), exported throughsrc/lib.rs. Reuse Input/Button composition and scoped-action idioms fromsrc/conversation_find.rs/src/terminal_find.rs, without importing conversation labels/actions into Review. Keep matcher/stepping pure and UTF-8-safe. Ranges index GPUI Kit's rendered text, not Git bytes or Markdown-source offsets. Add controlled find state and tracked scrolling/on_revealto the Review body; use measured bounds, not estimated line heights. Preserve the syntax renderer and text selection. - Native host:
crates/threadlane-ui-right-panel/src/{view.rs,types.rs}owns query, focus, cancellation and generation state. ReuseReviewDiffRequestguards and accepted-result installation. Wait for the currentTextViewStateparse before scanning; validate target, query generation and rendered revision before publishing highlights/results. Reset via existing document/surface transitions. Presentation stays in the kit; Git loading/checkout guards stay in the host. No daemon/protocol, persistence or Git-mutation changes. - Preview: reuse the same component in
crates/threadlane-ui-kit/examples/preview/{review.rs,review_tests.rs}over captured single/combined patches. Include removed text, Unicode, no matches and long off-screen fixtures. No live Git/host-filesystem reads. - Tests and design review: add pure matcher/state tests plus Review interaction tests in
crates/threadlane-ui-right-panel/src/{tests.rs,view.rs}. Mount initializedgpui_component::Root; usesimulate_inputfor Change events. Follow AGENTS.md and GPUI Kit Design Guides: restrained inline hierarchy, native commands, explicit focus ownership, visible states and theme tokens.
Acceptance criteria and validation
- Find phrases existing only in deleted lines, as well as additions/context/headers/off-screen occurrences, in single-file and All changes. Correct counts/ranges for repeated matches, CRLF, Unicode case folding and non-ASCII byte boundaries.
- Active matches visibly scroll into view, including near a long patch's bottom. Highlights do not rewrite copied text or replace selection. Verify in a real window, not just successful API returns.
- Rapid queries, delayed parse/search, refresh, whitespace toggles, Previous/Next file, checkout/session changes and Back/Close cannot install stale ranges, scroll another document or crash.
- Loading/error/empty/binary-summary/no-match/capped states are truthful. Multi-megabyte patches stay responsive. No agent, Git mutation or search beyond displayed patch data.
- Keyboard-only open/type/navigate/close, IME, focus restoration, nested-menu Escape, narrow layouts, increased fonts and light/dark/custom themes work. Preview remains snapshot-only; conversation/editor/terminal find and Review file navigation/Ignore whitespace regressions are covered.
Planned commands: cargo nextest run -p threadlane-ui-kit -p threadlane-ui-right-panel, cargo check -p threadlane-gpui, git diff --check, plus preview's documented native/WASM checks. Manually use a disposable checkout with a large combined patch and delayed responses. Implementation tests and visual checks have not been run for this research-only filing.
Duplicate audit and alternatives
Read all 104 open/closed issue bodies, 32 issue comments, and GitHub Discussions (none). Searched concepts/synonyms including find/search in diff, patch text, removed/deleted lines, hunk navigation and review navigation; also inspected current implementation.
- #402: local Previous/Next file, now implemented—not phrase navigation inside patches.
- #377: saved checkout files, not deleted patch text.
- #333: Ignore whitespace, retained unchanged.
- #311: GitHub PR Viewed/Next unviewed, not local text search. #156 covers GitHub lifecycle/readiness workflows.
- #267, #293: conversation/terminal find—patterns to reuse, not duplicate scope.
Draft stash, editor conflict protection, selected-text-to-chat, Markdown preview and recent-file recovery were excluded as implemented/already covered. A hunk/file navigator is less direct for a known phrase; repository/history-wide diff search is much broader. Current-document find is the strongest bounded opportunity without backend expansion.
Risks / tradeoffs
The main risk is parsed/rendered range identity and real scroll integration, not the input field. Parsing is asynchronous; existing Markdown escaping means raw patch offsets are unsafe. Search committed rendered snapshots and reject stale results. Loaded-document scope intentionally omits hidden full-file context, other commits and GitHub PRs. The match cap bounds highlight memory but must be visible. Do not turn this into a generic search framework or diff-renderer replacement.
Activity