feat(ui): the search palette, entry points and a trail that survives the URL (CG-45)

Search: `/` or ⌘K focuses the box; results arrive grouped by kind with their
glyph, signature and file:line, ↑/↓/Enter walk them, Esc dismisses. A group
appears where its best result did, so flattening the groups reproduces the
ranking the keyboard walks — the panel's flat item list IS that concatenation.
A flow question ("how does X reach Y", "X -> Y") is recognised and searches
both endpoints with a note, rather than offering a row that would land on the
phase-2 Flow view.

Entry points answer "where do I start" on the empty screen and in the resting
palette, all derived from the graph: routes, files that run something at module
level (the engine records a top-level statement as an edge out of the file node,
which is what makes src/bin/codegraph.ts the root of the CLI flow — ranked by
calls x the files they reach, so a registration table calling into itself does
not outrank the CLI), and the most depended-on symbols. Tests are excluded from
both derived lists.

Trail: hops record the direction they were walked (→ into a call, ← up to a
caller), clicking one truncates back to it, Clear keeps the place instead of
throwing it away, and the whole walk travels in the URL. A shared or reloaded
trail arrives as ids, so hops learn their names back through a new batch
endpoint and a session name cache — without it, walking back across a
truncation redrew earlier hops as raw hashes. "Read as flow" stays hidden until
there is a Flow view to send it to.

New endpoints: /api/entrypoints and /api/nodes. New engine reads:
getTopCallingFiles, getFileDependentCounts.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Colby McHenry
2026-08-27 00:54:18 -05:00
co-authored by Claude Opus 5
parent e9596af1cf
commit 87afc50e76
20 changed files with 1926 additions and 67 deletions
+23
View File
@@ -1377,6 +1377,29 @@ export class CodeGraph {
return this.queries.getTopDependedOn(limit);
}
/**
* The graph's executable roots — files that run something at module level (a
* CLI, a worker entry, a script), ranked by calls x the number of other files
* they reach. A statement at the top level of a file is recorded as an edge
* out of the *file* node, which is what makes these visible at all.
*/
getTopCallingFiles(
limit: number
): Array<{ nodeId: string; filePath: string; calls: number; reaches: number; score: number }> {
return this.queries.getTopCallingFiles(limit);
}
/**
* How many other files depend on each of the given files, counted through
* their symbols (an `imports` edge points at the symbol, not the file).
* A zero means nothing else in the index reaches into that file.
*/
getFileDependentCounts(filePaths: string[]): Map<string, number> {
return new Map(
this.queries.getFileDependentCounts(filePaths).map((row) => [row.filePath, row.dependents])
);
}
/**
* References from a symbol that never resolved to an indexed node — the
* calls and type mentions that leave the index. Lets a reader account for