Files
opencode/specs/session-review-cross-diff-search-plan.md
2026-02-26 18:23:04 -06:00

7.2 KiB

Session Review Cross-Diff Search Plan

One search input for all diffs in the review pane


Goal

Add a single search UI to SessionReview that searches across all diff files in the accordion and supports next/previous navigation across files.

Navigation should auto-open the target accordion item and reveal the active match inside the existing unified File diff viewer.


Non-goals

  • Do not change diff rendering visuals, line comments, or file selection behavior.
  • Do not add regex, fuzzy search, or replace.
  • Do not change @pierre/diffs internals.

Current behavior

  • SessionReview renders one File diff viewer per accordion item, but only mounts the viewer when that item is expanded.
  • Large diffs may be blocked behind the MAX_DIFF_CHANGED_LINES gate until the user clicks "render anyway".
  • File owns a local search engine (createFileFind) with:
    • query state
    • hit counting
    • current match index
    • highlighting (CSS Highlight API or overlay fallback)
    • Cmd/Ctrl+F and Cmd/Ctrl+G keyboard handling
  • FileSearchBar is currently rendered per viewer.
  • There is no parent-level search state in SessionReview.

UX requirements

  • Add one search bar in the SessionReview header (input, total count, prev, next, close).
  • Show a global count like 3/17 across all searchable diffs.
  • Cmd/Ctrl+F inside the session review pane opens the session-level search.
  • Cmd/Ctrl+G, Shift+Cmd/Ctrl+G, Enter, and Shift+Enter navigate globally.
  • Navigating to a match in a collapsed file auto-expands that file.
  • The active match scrolls into view and is highlighted in the target viewer.
  • Media/binary diffs are excluded from search.
  • Empty query clears highlights and resets to 0/0.

Architecture proposal

Use a hybrid model:

  • A session-level match index for global searching/counting/navigation across all diffs.
  • The existing per-viewer search engine for local highlighting and scrolling in the active file.

This avoids mounting every accordion item just to search while reusing the existing DOM highlight behavior.

High-level pieces

  • SessionReview owns the global query, hit list, and active hit index.
  • File exposes a small controlled search handle (register, set query, clear, reveal hit).
  • SessionReview keeps a map of mounted file viewers and their search handles.
  • SessionReview resolves next/prev hits, expands files as needed, then tells the target viewer to reveal the hit.

Data model and interfaces

type SessionSearchHit = {
  file: string
  side: "additions" | "deletions"
  line: number
  col: number
  len: number
}

type SessionSearchState = {
  query: string
  hits: SessionSearchHit[]
  active: number
}
type FileSearchReveal = {
  side: "additions" | "deletions"
  line: number
  col: number
  len: number
}

type FileSearchHandle = {
  setQuery: (value: string) => void
  clear: () => void
  reveal: (hit: FileSearchReveal) => boolean
  refresh: () => void
}
type FileSearchControl = {
  shortcuts?: "global" | "disabled"
  showBar?: boolean
  register: (handle: FileSearchHandle | null) => void
}

Integration steps

Phase 1: Expose controlled search on File

  • Extend createFileFind and File to support a controlled search handle.
  • Keep existing per-viewer search behavior as the default path.
  • Add a way to disable per-viewer global shortcuts when hosted inside SessionReview.

Acceptance

  • File still supports local search unchanged by default.
  • File can optionally register a search handle and accept controlled reveal calls.

Phase 2: Add session-level search state in SessionReview

  • Add a single search UI in the SessionReview header (can reuse FileSearchBar visuals or extract shared presentational pieces).
  • Build a global hit list from props.diffs string content.
  • Index hits by file/side/line/column/length.

Acceptance

  • Header search appears once for the pane.
  • Global hit count updates as query changes.
  • Media/binary diffs are excluded.

Phase 3: Wire global navigation to viewers

  • Register a FileSearchHandle per mounted diff viewer.
  • On next/prev, resolve the active global hit and:
    1. expand the target file if needed
    2. wait for the viewer to mount/render
    3. call handle.setQuery(query) and handle.reveal(hit)

Acceptance

  • Next/prev moves across files.
  • Collapsed targets auto-open.
  • Active match is highlighted in the target diff.

Phase 4: Handle large-diff gating

  • Lift render anyway state from local accordion item state into a file-keyed map in SessionReview.
  • If navigation targets a gated file, force-render it before reveal.

Acceptance

  • Global search can navigate into a large diff without manual user expansion/render.

Phase 5: Keyboard and race-condition polish

  • Route Cmd/Ctrl+F, Cmd/Ctrl+G, Shift+Cmd/Ctrl+G to session search when focus is in the review pane.
  • Add token/cancel guards so fast navigation does not reveal stale targets after async mounts.

Acceptance

  • Keyboard shortcuts consistently target session-level search.
  • No stale reveal jumps during rapid navigation.

Edge cases

  • Empty query: clear all viewer highlights, reset count/index.
  • No results: keep the search bar open, disable prev/next.
  • Added/deleted files: index only the available side.
  • Collapsed files: queue reveal until onRendered fires.
  • Large diffs: auto-force render before reveal.
  • Split diff mode: handle duplicate text on both sides without losing side info.
  • Do not clear line comment draft or selected lines when navigating search results.

Testing plan

Unit tests

  • Session hit-index builder:
    • line/column mapping
    • additions/deletions side tagging
    • wrap-around next/prev behavior
  • File controlled search handle:
    • setQuery
    • clear
    • reveal by side/line/column in unified and split diff

Component / integration tests

  • Search across multiple diffs and navigate across collapsed accordion items.
  • Global counter updates correctly (current/total).
  • Split and unified diff styles both navigate correctly.
  • Large diff target auto-renders on navigation.
  • Existing line comment draft remains intact while searching.

Manual verification

  • Cmd/Ctrl+F opens session-level search in the review pane.
  • Cmd/Ctrl+G / Shift+Cmd/Ctrl+G navigate globally.
  • Highlighting and scroll behavior stay stable with many open diffs.

Risks and rollback

Key risks

  • Global index and DOM highlights can drift if line/column mapping does not match viewer DOM content exactly.
  • Keyboard shortcut conflicts between session-level search and per-viewer search.
  • Performance impact when indexing many large diffs in one session.

Rollback plan

  • Gate session-level search behind a SessionReview prop/flag during rollout.
  • If unstable, disable the session-level path and keep existing per-viewer search unchanged.

Open questions

  • Should search match file paths as well as content, or content only?
  • In split mode, should the same text on both sides count as two matches?
  • Should auto-navigation into gated large diffs silently render them, or show a prompt first?
  • Should the session-level search bar reuse FileSearchBar directly or split out a shared non-portal variant?