Files
backnotprop__plannotator/packages/server/code-nav.ts
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

74 lines
1.7 KiB
TypeScript

/**
* Code navigation — Bun runtime adapter and request handler.
*/
import {
type CodeNavRequest,
type CodeNavRuntime,
type CodeNavResponse,
resolveCodeNav,
validateCodeNavRequest,
extractChangedFiles,
} from "@plannotator/shared/code-nav";
export type { CodeNavRequest, CodeNavResponse };
const bunCodeNavRuntime: CodeNavRuntime = {
async runCommand(command, args, options) {
let proc;
try {
proc = Bun.spawn([command, ...args], {
cwd: options?.cwd,
stdout: "pipe",
stderr: "pipe",
});
} catch {
return { stdout: "", stderr: "command not found", exitCode: 1 };
}
let timer: ReturnType<typeof setTimeout> | undefined;
if (options?.timeoutMs) {
timer = setTimeout(() => proc.kill(), options.timeoutMs);
}
const [stdout, stderr, exitCode] = await Promise.all([
new Response(proc.stdout).text(),
new Response(proc.stderr).text(),
proc.exited,
]);
if (timer) clearTimeout(timer);
return { stdout, stderr, exitCode };
},
};
export async function handleCodeNavResolve(
req: Request,
cwd: string,
changedFiles: string[],
): Promise<Response> {
try {
const body = (await req.json()) as CodeNavRequest;
const error = validateCodeNavRequest(body);
if (error) {
return Response.json({ error }, { status: 400 });
}
const result = await resolveCodeNav(
bunCodeNavRuntime,
body,
cwd,
changedFiles,
);
return Response.json(result);
} catch (err) {
return Response.json(
{ error: err instanceof Error ? err.message : "Code navigation failed" },
{ status: 500 },
);
}
}
export { extractChangedFiles };