feat(ui): saved trails — a walk you named, kept, and still true after a re-index (CG-60)

Save trail on the trail bar writes the walk to .codegraph/ui/trails/ as one
JSON file, listed on the empty screen and on Entry points above the derived
suggestions, reopened at the symbol you left with the whole path restored.

A hop is stored by qualified name, kind and file — never by node id, which
contains a start line and so changes the first time anybody edits above the
symbol. Every hop is re-resolved against the current index on the way out and
each row says what became of it: still here, moved to another file, now
ambiguous, or gone. A hole is never stitched over: the row opens the longest
run of CONSECUTIVE resolved hops and says which ones those are, because the
trail is a path and a skipped hop would draw a call that does not exist.

This is the first write the viewer makes, and the boundary moved with it:
POST/DELETE answer under /api/ only, must carry X-CodeGraph-UI and
application/json (neither of which a cross-origin form can produce without a
preflight this server answers none of), and --read-only refuses both while
still listing what is there. The blanket "read-only" claim is retired from the
banner, the README, the CLI help and the docs site in favour of the narrower
true one.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Colby McHenry
2026-08-27 08:31:48 -05:00
co-authored by Claude Opus 5
parent 55a33055ee
commit 47576b392e
32 changed files with 3162 additions and 78 deletions
+7 -1
View File
@@ -18,7 +18,7 @@ and adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
Run `codegraph ui` in an indexed project, or `codegraph ui /path/to/project` for one indexed elsewhere (`codegraph web` is an alias). It takes port 4747, or the next free one; `--port <n>` pins a specific port and `--no-open` just prints the URL for a headless box or an SSH session. Set `CODEGRAPH_BROWSER=<command>` to choose the browser, or `CODEGRAPH_BROWSER=none` to never open one.
The viewer listens on `127.0.0.1` only, so nothing on your network can reach it, and requests claiming to come from any other host are refused. It is read-only: it opens an index that already exists, never creates one, and never writes to your project or your graph. It sends nothing anywhere.
The viewer listens on `127.0.0.1` only, so nothing on your network can reach it, and requests claiming to come from any other host are refused. It opens an index that already exists, never creates one, and never changes your graph or a line of your code — the one thing it writes is a trail you asked it to save (see below), and `--read-only` turns even that off. It sends nothing anywhere.
- **A map of the whole project, in `codegraph ui`.** The Map tab draws your repository at module granularity — one box per directory — with dependencies pointing down, so the top of the picture is what runs first and the bottom is what everything else stands on. Nothing is placed by hand and nothing floats: a module sits one layer above whatever it depends on, line weight is how many calls, imports and type references cross the link, and the same project always draws the same picture. Hover a link for what crosses it, including the busiest symbol pairs behind the weight; click a module to isolate its links, list its dependencies and dependents with counts, and jump straight into one of its files.
@@ -80,6 +80,12 @@ and adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
On the **Map**, a module nothing depends on now says so in its own count line instead of counting itself — usually your entry points, sometimes something you forgot to delete. Tool-generated files and modules are dimmed wherever they appear: on the map, in its file list, in search results and on the file screen.
- **Keep a walk you want to come back to: saved trails in `codegraph ui`.** Follow a path through the code, press **Save trail** on the trail bar, give it a name, and it's kept. Saved trails are listed on the empty screen and on the Entry points tab, above the suggestions — a walk somebody named beats any ranking — and opening one puts you back at the symbol you left with the whole path in the trail bar. Explaining "how a request is served" to a new teammate is now a link and a name rather than a paragraph.
A saved trail survives your project changing. Each step is remembered by what it is — its qualified name, its kind, the file it was in — rather than by where it sat, so editing the file above a function doesn't lose it. When something does move, the trail says so on its own row: which step moved to another file, which one was renamed or deleted, and which part of the walk still opens. It never quietly stitches over a missing step, because a trail is a path and a step that skips one would show a call that doesn't exist.
Trails are plain JSON, one file per trail, under `.codegraph/ui/trails/` — already ignored by git, so they stay yours by default. **Export** hands you the file if you'd rather commit one for the team. This is the only thing the viewer writes: it still never indexes, never changes your graph, and never touches a line of your code. Start it with `codegraph ui --read-only` and it won't write even that — saved trails can still be opened, just not saved or deleted.
### Fixes
- Fixed a long-running `codegraph ui` session serving a symbol that a sync had already deleted. The viewer keeps one connection to your index open, and its in-memory lookup didn't notice when another process — your agent's sync, or `codegraph sync` — rewrote the file underneath it, so a symbol screen could keep showing a body with no callers while search correctly reported it had moved. Because a symbol's identity includes the line it starts on, this happened after almost any edit above it.