Skip to content

feat(search): / searches diff content via a bundled search extension - #1096

Merged
elucid merged 11 commits into
mainfrom
bundled-content-search
Sep 11, 2026
Merged

feat(search): / searches diff content via a bundled search extension#1096
elucid merged 11 commits into
mainfrom
bundled-content-search

Conversation

@elucid

@elucid elucid commented Sep 11, 2026

Copy link
Copy Markdown
Member

Problem

/ focuses the file filter, and there is no way to search the content of a review. elucid/hunk-less-search fills the gap as a third-party extension, but only by faking a prompt: a permanently open bottom pane, a keyboard mode re-implementing a line editor, and a store token deferring navigation to the pane's useEffect. Review and hunk log also disagree about what /, n, and N mean.

Approach

Bring the search into Hunk as a bundled extension built on the PR 1 status-line primitive, and give it /, n, N by default.

  • ctx.selection.files (extension API 27). The visible files in review order — the same frozen views a pane's files prop carries — so a whole-review command reads its corpus from the selection instead of shadow-tracking changeset_loaded. Hidden (filtered) files are therefore never search targets.
  • Bundled composition grows. getBundledUIRegistry() was only composed for panes; ui/lib/sessionRegistrations.ts now merges bundled commands and line highlighters ahead of the user registry's at the App merge points (bundled first, so a user extension can never shadow them, matching the existing key-conflict rule). hunk log treats bundled command ids as known-but-inactive, bundled prompts/dialogs omit the ext attribution by vendor id, and the registry no longer requires every bundled factory to register a pane. Bundled event handlers are deliberately not composed — nothing needs them.
  • packages/hunk/src/extensions/default/ui/search/. The pure primitives (search.ts) and their tests are ported nearly verbatim from hunk-less-search. The session (session.ts) is rewritten to take files per call and rebuild targets when the list changes identity (a reload keeps the query but orphans the old current target). The registration (index.ts) is three commands plus one line highlighter: /ctx.prompts.line({ prefix: "/" })revealLine on the first matching line (falling back to selectHunk when the patch never numbered it) → ctx.highlights.refreshctx.statusLine.set. The store, prompt grammar, pane, and deferred-navigation token are gone.
  • Prompt semantics. Reopening / prefills the last query. Enter re-runs it; Escape clears the buffer; Escape again cancels and leaves the search (marks, status item, n/N) in place; submitting the emptied prompt ends the search and frees the status row. Smart case, literal matching, strict wrapping repeats from the live selection, [i/n] path:line (+k in hunk) • wrapped — text on the status row, every match marked in the diff with the landed line inverted.
  • Keys. hunk.review.focusFilter and hunk.review.nextNote / previousNote keep their ids and behavior but ship unbound. Tab and the File/Navigate menus still reach the filter; } / { still step annotated hunks. [keybindings] "hunk.review.focusFilter" = "/" restores the old chord and the exclusive-binding resolver silently takes / from search (no conflict warning). Search sits under Navigate and in the help dialog; hunk.* bundled commands are filtered out of the Extensions menu.
  • Migration. A still-installed hunk-less-search gets one startup notice pointing at hunk extension remove hunk-less-search; its stale [keybindings] line is already reported softly. The website directory entry is removed.

Non-goals: no [search] mode = "regex" config (the bundled registry is process-cached and factories run with no config); the regex branch is kept and tested for a follow-up. No bundled event composition. No changes to hunk log search.

Stacked on #1095 (status-line-primitive); merge that first, then this. docs/handoff-pr2-bundled-search.md is local and not committed. Note that docs/status-line-extension-point.md was committed on the PR 1 branch despite being marked local-only there.

Why bundled rather than native

Porting the search straight into App would be less code, but reviewInfo already set the precedent that bundled UI goes through the public API, and doing it this way closes the composition gap (commands and highlighters from a bundled factory were dropped on the floor) instead of leaving it as a trap for the next bundled feature.

Tests

Unit (colocated): extensionSelection.test.ts (files), sessionRegistrations.test.ts (bundled-first composition, --no-extensions shape, vendor-id attribution), search/search.test.ts (ported), search/session.test.ts (strict wrapping, follow-the-selection, corpus change, bad regex keeps the old query, hidden files never targeted, clear), startup.test.ts (superseded-install notice), appMenus/helpContent/appCommands (Navigate rows, Extensions-menu filtering, unbound rows, docs table includes bundled ids).
App-level: new AppHost.search.test.tsx — mounted with user extensions disabled: / prompt without attribution, Enter lands near the viewport top with the inverted current mark, n/N step and wrap, miss / no-query / emptied-prompt reports, Escape×2 keeps the search, reload keeps the query, filtered-out file never targeted, Tab still opens the filter.
PTY: new test/pty/search-integration.test.ts (prompt, line-exact reveal, n/N, current + tinted match cell backgrounds, Tab, Escape/clear flows, and the "hunk.review.focusFilter" = "/" remap). Existing PTY/AppHost tests that pressed / for the filter now press Tab; the chrome slash case exercises the documented remap; the notes note-stepping case binds n/N back through [keybindings].

Commands run

  • bun run typecheck, bun run lint, bun run deps:check — clean. bun run generate:skill — no diff. bun run format:check — only untracked local files flagged.
  • bun run test — 4277 pass; 5 failures also fail on origin/main in this environment (packages/hunk-jj "logs unexpected source failures", test/cli/install-vm ×3, AppHost.watch observer-debounce). Reproduced on a clean origin/main worktree.
  • bun run test:integration — 173 pass; 5 failures also fail on origin/main here (extensions-integration trust-state ×2, lifecycle SIGHUP/SIGPIPE/SIGQUIT). The one new failure the run surfaced (notes stepping with n/N) was fixed by binding the keys in that test; it passes now.
  • bun run test:tty-smoke — skips on macOS.
  • Real TTY (tmux, empty XDG_CONFIG_HOME, hunk diff HEAD~3 --mode unified): / prompt with cursor, Enter lands and reports [i/n] path:line, n/N step and wrap, Tab opens the filter beside the search item, Escape×2 keeps the search, Escape+Enter clears it and n then says "No search yet", Navigate menu shows the three entries and help shows / and n / N.

Platforms: macOS only.

Follow-ups

  • [search] mode = "regex" config once bundled factories can read config.
  • Stepping is hunk-granular (every match in one hunk is one target), so n inside a single tall hunk wraps to itself; line-granular stepping within a hunk is a possible refinement.
  • Archive elucid/hunk-less-search with a README pointer (outside this repo).

@vercel

vercel Bot commented Sep 11, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated
hunk-web Ignored Ignored Preview Sep 11, 2026 6:20pm UTC

Request Review

@greptile-apps

greptile-apps Bot commented Sep 11, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR adds bundled, review-wide diff-content search and exposes visible review files through extension API 27.

  • Registers /, n, and N search commands plus match highlighting and status-line reporting.
  • Composes bundled commands and line highlighters ahead of user extension registrations.
  • Moves the file filter and note-stepping commands off their former default search keys while preserving remapping.
  • Adds broad unit, mounted-app, and PTY coverage for navigation, filtering, wrapping, prompt behavior, and highlighting.
  • Correctly addresses both findings from the previous review by preserving query whitespace and marking repeated occurrences.

Confidence Score: 5/5

The PR appears safe to merge; no actionable new defects or outstanding previous findings remain.

The repeated-match implementation now emits every non-overlapping occurrence, and search compilation and session state preserve surrounding whitespace. The other changes since the previous review maintain prompt and search lifecycle behavior without introducing a demonstrated regression.

Important Files Changed

Filename Overview
packages/hunk/src/extensions/default/ui/search/search.ts Implements diff parsing, smart-case matching, repeated-range collection, target ordering, and match highlighting; both prior findings are fixed.
packages/hunk/src/extensions/default/ui/search/session.ts Maintains the query and hunk-granular targets while rebuilding against changed visible-file snapshots.
packages/hunk/src/extensions/default/ui/search/index.ts Registers bundled search commands and highlighting and connects outcomes to navigation and the status line.
packages/hunk/src/ui/lib/sessionRegistrations.ts Composes bundled commands and highlighters before user registrations while retaining extension-resolution behavior.
packages/hunk/src/extension-api/types.ts Advances extension API version 27 and adds the frozen visible-file collection to command selections.
packages/hunk/src/ui/App.tsx Supplies visible files through command selections and consumes composed bundled registrations.
packages/hunk/src/extensions/default/ui/search/search.test.ts Covers parsing, smart case, literal and regex behavior, repeated non-overlapping ranges, targeting, and marks.
packages/hunk/src/ui/AppHost.search.test.tsx Exercises bundled search end-to-end in the mounted app, including navigation, wrapping, filtering, reloads, and prompt semantics.

Sequence Diagram

sequenceDiagram
    participant U as User
    participant C as Bundled search command
    participant P as Status-line prompt
    participant S as Search session
    participant N as Review navigation
    participant H as Line highlighter

    U->>C: Press /
    C->>P: Open prompt with previous query
    P-->>C: Submitted query
    C->>S: Search visible selection.files
    S-->>C: Next hunk target and match ranges
    C->>N: Reveal matching line
    C->>H: Refresh search marks
    C-->>U: Show match position on status line
    U->>C: Press n or N
    C->>S: Repeat from live selection
    S-->>C: Next/previous target, wrapping if needed
    C->>N: Reveal target
    C->>H: Refresh current mark
Loading

Reviews (2): Last reviewed commit: "fix(search): keep literal whitespace in ..." | Re-trigger Greptile

Comment thread packages/hunk/src/extensions/default/ui/search/search.ts Outdated
Comment thread packages/hunk/src/extensions/default/ui/search/search.ts Outdated
@elucid
elucid force-pushed the status-line-primitive branch from b754d95 to e98f224 Compare September 11, 2026 13:45
@elucid
elucid force-pushed the bundled-content-search branch 2 times, most recently from 046a62d to c4208e4 Compare September 11, 2026 14:16
`ctx.selection.files` carries the visible review files in review order, the
same frozen views a pane's `files` prop receives, so a whole-review command
reads its corpus from the selection instead of shadow-tracking
`changeset_loaded`. Bumps the extension API to 27.
… the session

Bundled UI registrations were only composed for panes; commands and line
highlighters a bundled factory registered were dropped. `sessionRegistrations`
merges both ahead of the user registry at the App merge points, the history
surface treats them as known-but-inactive ids, bundled prompts and dialogs
recognize the vendor id for attribution, and the bundled registry no longer
requires every factory to register a pane.
Ports elucid/hunk-less-search's patch parsing, smart-case query compiling,
hunk-granular target discovery, match marks, and wrapping step logic with
their tests, unchanged apart from import paths and shared test fixtures.
The session no longer shadows the changeset: search and repeat receive the
visible files from the command selection and rebuild targets when that list
changes identity, so hidden files never become targets and a reload keeps the
query while orphaning the old current target. Adds `clear` for an empty
submit and renders outcomes as status-line spans.
`hunk.review.focusFilter`, `hunk.review.nextNote`, and `hunk.review.previousNote`
keep their ids and behavior but ship without default keys so content search
can take `/`, `n`, and `N`. Tab and the menus still reach the filter, `}` / `{`
still step annotated hunks, and `"hunk.review.focusFilter" = "/"` restores the
old chord. Tests that used `/` to reach the filter now press Tab, and the PTY
slash case exercises the documented remap.
`/` opens the status-line prompt and Enter jumps to the next hunk matching the
typed text; `n` / `N` repeat and wrap; the status row reports each landing and
the diff marks every match with the landed line inverted. Registered as
bundled UI under the `hunk` vendor id (`hunk.search.find` / `next` /
`previous`), listed under Navigate and in the help dialog, and documented
beside the built-in keybindings.
Its `/` search is now bundled, so a lingering install only duplicates keys
(bundled commands win the chord). Startup reports one notice pointing at
`hunk extension remove hunk-less-search`.
Covers the prompt, the line-exact reveal near the viewport top, n / N
stepping and wrapping, the inverted current mark and tinted match marks, Tab
still reaching the filter, the emptied-prompt clear, and the documented
`"hunk.review.focusFilter" = "/"` remap taking the key back from search.
@elucid
elucid force-pushed the bundled-content-search branch from c4208e4 to a288fc0 Compare September 11, 2026 18:20
@elucid
elucid changed the base branch from status-line-primitive to main September 11, 2026 18:20
@elucid elucid closed this Sep 11, 2026
@elucid elucid reopened this Sep 11, 2026
@greptile-apps

greptile-apps Bot commented Sep 11, 2026

Copy link
Copy Markdown
Contributor

Want your agent to iterate on Greptile's feedback? Try greploops.

@elucid
elucid merged commit 0a41a76 into main Sep 11, 2026
15 checks passed
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.

1 participant