2 Commits

Author SHA1 Message Date
Michael Ramos 91ad7c95fc feat(review): token hover cards, Tier 0 (#1461)
* feat(review): add POST /api/code-nav/hover in both runtimes

Tier 0 of the token hover card: the hover pipeline is the ripgrep search
/resolve already runs, plus three cheap enrichments read off the same
machinery.

- packages/shared/code-nav.ts: definition patterns carry the kind they
  prove (alternations split one-per-kind, so definition-vs-reference
  classification is byte-identical and classifyMatch becomes a thin
  wrapper over classifyMatchDetailed); scanDocComment, buildSignature and
  resolveCodeNavHover; an additive timeoutMs option on resolveCodeNav so
  hover can ask for 3s while /resolve keeps its 5s; an optional readFile
  member on CodeNavRuntime so /resolve callers stay unchanged.
- Both review servers gain /api/code-nav/hover behind the same guard
  stack as /resolve. /resolve itself is untouched.

The doc scan is conservative by construction: per-language, blank-line
separated, capped, and null for an unknown language. Returning nothing
always beats returning garbage.

* feat(review): token hover cards in the code-review diff

Resting the pointer on a symbol opens a card with where it is defined, an
approximate signature, its doc comment if the scan found a real one, and a
sample of its references. Every location on the card routes into the same
References panel Cmd+click opens.

- utils/stitchTokenIdentifier: rebuilds one identifier from the token spans
  Shiki fragmented it into, using each span's data-char column to prove
  adjacency. It stops at dots (rg searches with --word-regexp, where a
  dotted path matches nothing) and refuses keywords and one-character
  names, which is what keeps most hovers off the wire entirely.
- hooks/useTokenHover: 350ms dwell before any request exists, one in-flight
  request aborted by its successor, a 30-entry LRU flushed whenever the diff
  snapshot changes, a 250ms leave grace so the card's own links are
  reachable, and a scroll/wheel cancel because the anchor rect is stale the
  moment the pane moves. An unavailable backend, a failure, a timeout and a
  thin answer all render nothing, silently: a hover is an idle gesture and
  must never nag.
- components/TokenHoverCard: portaled to body so it escapes the Dockview
  overflow and stacking context; anchored below the token, flipped above
  when the viewport would clip it. It shows what the search found and
  nothing it did not, so uncertainty is a second location line rather than
  a description of the ranking.
- Wiring: two optional props beside onCodeNavRequest in both diff views,
  passed only when the existing live-workspace gate AND the new cookie-only
  "Token hover cards" setting are on. Off means no listeners, no requests
  and no card in the tree. Alt+click joins Cmd+click as an unadvertised
  alias into the References panel; the meta/ctrl branch is unchanged.

The guides.show viewer manifest moves with this: AllFilesCodeView is in the
portable viewer's graph, so its new optional props shift the bundle hash.

* fix(review): correct token hover supersession, scroll and doc-scan defects

Review findings, each with the regression test that fails without the fix.

- An open card could be rewritten by a NEIGHBOUR's answer: drifting onto an
  adjacent token launched its request, and returning to the open card took
  the same-key early return without reclaiming the active key, so the
  neighbour's answer still passed the landing check. The early return now
  reclaims the key, kills the pending dwell, and aborts a foreign request.
- Re-entering a token inside the leave grace re-armed the dwell while that
  token's own request was still in flight, spawning a second ripgrep for an
  answer already on its way. The dwell now joins the in-flight request, and
  the answer anchors to the span the pointer is on now.
- Scrolling INSIDE the card closed it, which made the signature block's
  horizontal scroller unreadable by the gesture meant to read it. The
  cancel now ignores events originating in the card's own subtree; a pane
  scroll still closes it.
- A below-threshold answer for a different token left the previous token's
  card standing over a symbol the reviewer had already left.
- The doc scan rendered tooling directives as documentation. Directives are
  dropped from BOTH ENDS of the comment run — eslint-disable, @ts-*,
  prettier-ignore, biome-ignore, istanbul ignore, noqa, type: ignore and
  triple-slash references — because the commonest real position is the line
  immediately above the definition, which is the trailing end of the run as
  collected. Never from the middle: a directive surrounded by prose sits
  inside documentation we would have to interpret to cut safely. A run that
  is nothing but directives returns null, and prose that merely mentions a
  directive is untouched.

Also: an answer whose token has been recycled out of the DOM opens no card
(a detached rect is 0,0 and would pin it to the viewport corner); a flipped
card is clamped to the top edge; the card is a tooltip, not a dialog; a
location click describes the CLICKED location rather than forwarding the
hover's charStart and language into another file; definition.preview stays
declared but unpopulated until a consumer exists; the overflow line regains
its leading ellipsis and now renders under the banned-vocabulary sweep.

Portable viewer: the hover prop is inverted to (props, filePath) so the two
diff views import nothing new and stitching lives in App. The read-only
guide viewer bundle no longer carries the stitcher or the request builder.
2026-09-02 19:00:39 -07:00
Michael Ramos 95750ab657 feat: search-based code navigation with peek view (#711)
* feat: search-based code navigation with peek view (#694)

Add IDE-like code navigation to the review UI. Cmd/Ctrl+click a token
in a diff to find its definitions and references across the repo via
ripgrep, displayed in a VS Code-style peek panel below the diff.

Backend: bounded rg search with language-aware definition patterns
(TS/JS, Python, Go, Rust), ranked results (same file > changed files >
same directory), confidence labels, and graceful degradation when rg
is not installed. Both Bun and Pi servers implement the endpoints.

Frontend: Dockview peek panel with syntax-highlighted full-file preview
on the left and grouped reference list on the right. Clicking a
reference scrolls the preview; double-clicking an in-diff result
navigates to the file with a gold line flash.

Closes #694

* fix: resolve Pi server TypeScript errors for code-nav

Add missing spawn import, type the close callback parameter,
and use double-cast for parseBody → CodeNavRequest.

* fix: clear loading state on cached preview hits

Without this, clicking a cached file while a fetch is in-flight
leaves isLoading stuck true — the spinner hides the preview.

* feat: show pointer cursor on Cmd/Ctrl+hover for navigable tokens

Adds pn-token-nav class with thicker underline and pointer cursor
when hovering a token while holding the modifier key, signaling
the token is Cmd+clickable for code navigation.

* refactor: remove go-to-diff navigation from peek panel

Strip the in-diff badge, double-click-to-jump, highlightDiffLine
wiring, and onCodeNavGoToDiff from the peek panel. The peek view
is the primary interaction — jump-to-diff adds complexity without
clear value at this stage.

* chore: remove dead code from code-nav cleanup

Delete unused highlightDiffLine.ts, remove codeNavChangedFiles
and codeNavActiveSide from context and App.tsx, drop stale
extractChangedFiles import.

* chore: add code-nav endpoints to AGENTS.md, remove dead activeSide state

* fix: don't classify bare indented calls as definitions

Change the TS/JS method pattern from zero-or-more (*) to
one-or-more (+) declaration keywords, so plain calls like
startServer(config) are no longer misclassified as definitions.

* feat: show toast when code-nav is unavailable in platform-only PR mode

Instead of opening the peek panel and showing misleading "No results",
Cmd+click in non-local PR mode shows a brief toast explaining that
code navigation requires a local checkout.

* fix: strip hljs hardcoded background from code-nav preview

The highlight.js github-dark theme sets a fixed dark background
on all .hljs elements. Apply transparent override for the entire
peek preview so code inherits the active theme's background.
2026-05-13 12:24:37 -07:00