Commit Graph
3 Commits
Author SHA1 Message Date
Colby McHenryandClaude Opus 5 bd99c5e99a feat(ui): the whole file — full source with gutter ports and intra-file call arcs (CG-52)
The File view gains a Source tab: the file itself, top to bottom, with the
Symbol view's line grid, gutter ports and call-site links, a line-anchored
callee rail, and — in the left margin — an arc for every call that stays inside
the file, drawn from the calling line to the callee's definition line.

The arcs are the point. Source order is already a layout, chosen by whoever
wrote the file, so a file's internal call structure can be drawn with no
algorithm placing anything. Crabviz's idea, in the one place it is legible.

Everything is arithmetic, not measurement. The Symbol view queries the laid-out
DOM to place a callee row beside its line; a 6 820-line file cannot afford that.
Here a line is exactly 20px at `10 + (n - 1) x 20`, so ~90 line elements exist at
a time and the arcs, ports, rail rows and connectors are all functions of a line
number. `src/mcp/tools.ts` scrolls at a 16.6ms median frame.

- `GET /api/filecode/<path>` — outline, one call group per (caller, callee) PAIR
  with its call-site lines, unresolved references, and the file's length. The
  source is NOT in it: it pages through `/api/source` 800 lines at a time with a
  discarded 150-line lead-in, so a page starting inside a block comment does not
  render prose as code, and so the ports and arcs are complete from the first
  frame while the text fills in behind them.
- `intraFileCalls` is counted over the groups actually returned, so the header
  and the picture under it cannot disagree once a cap bites.
- Above 40 arcs the diagram narrows to the symbol under the pointer (or the one
  the scroll position is inside) and the header states the total. Accent is for
  the pointer only, never for the filter.
- Sticky outline rail at >= 1400px, following the reader down the file.
- `QueryBuilder.getUnresolvedReferencesInFile` — one indexed lookup instead of
  one per symbol; `buildOutlineEntries` lifted out of `/api/file` so both
  readings of a file draw the same rows.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-27 03:47:42 -05:00
Colby McHenry 2ad836d935 feat(ui): highlight source server-side with a near-monochrome Shiki theme (CG-43)
The viewer's code block stops lexing with a hand-rolled dialect table and
reads real TextMate grammars instead, run once in `/api/source`.

Three things make that safe to depend on:

* Highlighting never fails a request. A missing grammar, an oversized
  slice, an ESM import that did not resolve — every one of them answers
  `engine: 'plain'` with a reason and the source still goes out.
* Identifiers survive whatever token boundaries a grammar chose. Every
  code token is split into identifier runs before it goes on the wire, so
  the graph's call-site overlay claims a token the highlighter produced
  rather than re-cutting the line. `assignRefs` now matches on a token's
  text rather than on the class a grammar gave it, so a language that
  scopes type names as `storage.type` still links.
* The theme classifies rather than colours: its foregrounds are sentinels
  the server maps back to class names, and the viewer paints them from
  CSS custom properties — one token stream serves light and dark with no
  refetch, and the ramp lives only in app.css.

Comments move from --ink-3 to a new --code-comment. --ink-3 measures
3.46:1 on paper and 3.00:1 on the hot-line tint, both under AA for 12.5px
text; --code-comment is the smallest step along the same ramp that clears
4.5:1 on every background a code line can have, and stays quieter than
the strings and numbers above it.

Shipping: @shikijs/core and @shikijs/engine-javascript are runtime
dependencies (no wasm, no native module); @shikijs/langs stays a
devDependency and `npm run build:textmate` writes only the closure the
engine's 40-odd languages reach — 56 grammars, 2.6 MB, against 11 MB for
all 722. check-ui-build.mjs asserts the tree after every build and inside
every release archive.
2026-08-27 01:23:10 -05:00
Colby McHenryandClaude Opus 5 951ba3678a feat(ui): read-only JSON API over the index for the viewer (CG-42)
Six endpoints under `/api/`, one per screen, each answering in a single
round-trip in the spirit of `codegraph_explore` — the viewer should never
have to ask a follow-up question to finish drawing a pane:

    /api/stats                     index state, graph counts, frameworks
    /api/search?q=                 ranked, kind-grouped symbol search
    /api/node/<id>                 rails, members, tests, blast radius
    /api/source?file=&from=&to=    verbatim source + a drift verdict
    /api/file/<path>               outline and import rails
    /api/routes                    URL -> handler, when there is one

It is a reader of the existing schema: no extraction or resolution changes.
It mounts on the `api` seam `startUiServer` already exposed, so it sits
behind the CG-41 loopback boundary — Host allowlist, no CORS headers,
GET/HEAD only — and every read out of the repository goes through
`resolveProjectFile`, ahead of the index lookup so a traversal is refused
as a traversal rather than reported as "not indexed".

Three properties the endpoints are built around:

- No N+1. The engine's busiest symbol has 545 incoming edges; resolving
  those one `getNode` at a time is 545 queries. Every edge list is
  resolved with one batched lookup, which needed four additive read-only
  query methods (`getNodesByIds`/`getFanIn`/`getFanOut` on `CodeGraph`,
  plus batched outgoing/incoming edge fetches and unresolved-reference
  reads). `/api/node` on `LRUCache.get` answers in ~10 ms.

- Capped lists, honest totals. 545 callers cannot all be rows, so caller
  groups cap at 300 — but `total` is always the real number, and the
  ordering puts the useful end first (same file, then production code,
  then tests). Every count in the payload is the length of a list the
  same payload returns, so a badge and its rail cannot disagree.

- Nothing overclaims. Source that drifted on disk since the last index
  sync is omitted rather than sliced at line ranges that may now point at
  a different symbol; calls that leave the index are counted instead of
  silently shortening the callee rail; imports that never resolved are
  named; and a test-coverage claim reports whether its search actually
  finished. `/api/routes` says a project simply is not routed, and
  refuses a `limit` below three because the engine's manifest would
  answer that question wrongly.

Tests: 45 against a real indexed fixture over a real loopback server,
covering every endpoint's shape, the drift verdict in all three places it
surfaces, search ranking and the filter grammar, the refusals, and the
capping/latency behaviour at 500 callers. The issue's own acceptance case
— `lru-cache.ts` `get` under 100 ms — runs against this repo's index when
one is present.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-26 23:33:30 -05:00