`GET /api/map` rolls the whole edge table up to module granularity in one `GROUP BY`, and the Map tab draws it: one box per directory, dependencies pointing down, nothing placed by hand. Two decisions carry the screen. The vertical order rests on each link's `declared` weight — the edges resolved through an import, a qualified name, an inheritance clause or a typed receiver — not on its raw count. Bare name matching resolves `run`, `push` and `finish` across unrelated directories, and layering on raw counts put `src/db` directly under `src/bin` on this repository's own index. On declared edges the same data reproduces the pipeline CLAUDE.md describes, with a third of the mutual pairs. When too few links carry a declared edge to describe a project, the layout falls back to raw counts and the side panel says so. And the aggregation is a single scan. Grouping by the symbol names as well as the modules costs nothing extra — the join is what is expensive — so one query yields both the link weights and the tooltip's symbol pairs. Measured against this index inflated to 800k edges: 1.28s for one scan against 1.89s for two, which is the difference between meeting and missing the cold budget on a ten-thousand-file repository. Cached answers come back in ~3ms. Nothing is dropped silently: thin links are hidden until a module they touch is selected and counted in the panel, uncertain references are excluded from every number on screen and the total is printed, and mutual dependencies, module loops and file-level circular imports are listed rather than straightened away. An edge that still points up after layering is drawn dashed on selection instead of being reversed or removed. The layout — cycle-breaking, longest-path layering, barycenter ordering, ports — is a pure function of the payload in `ui/src/lib/map-model.ts`, so the tests toggle and the selection cost no round-trip and the same project always draws the same picture. Svelte Flow supplies pan, zoom and fit; never a layout. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
68 lines
2.8 KiB
Markdown
68 lines
2.8 KiB
Markdown
# ui/ — the `codegraph ui` viewer
|
|
|
|
The browser reader for an indexed project: Svelte 5 + Vite, built as static
|
|
files and served by the CLI over loopback. An npm workspace of the engine, so
|
|
`npm ci` at the repo root installs its toolchain; nothing here is a runtime
|
|
dependency of the engine and nothing here is published to npm on its own.
|
|
|
|
Design spec (every token, size and measurement):
|
|
`../docs/design/codegraph-ui-design-spec.md`.
|
|
|
|
## Build
|
|
|
|
```bash
|
|
npm run build # from the repo root: tsc -> copy-assets -> this app
|
|
npm run build:ui # just this app, plus the dist assertion
|
|
npm run dev -w ui # Vite dev server on 127.0.0.1:5174
|
|
npm run check -w ui # svelte-check
|
|
```
|
|
|
|
`npm run build` emits **`dist/viewer/`** (`index.html` + hashed assets).
|
|
`scripts/check-ui-build.mjs` then asserts the tree is complete, so a broken UI
|
|
build fails the release instead of shipping a CLI that serves a 404. The same
|
|
check runs again in `scripts/build-bundle.sh` (after the bundle stage copies
|
|
`dist`) and in `scripts/pack-npm.sh` (after each archive is unpacked).
|
|
|
|
### Why `dist/viewer` and not `dist/ui`
|
|
|
|
`src/ui/` is the engine's **terminal** UI (shimmer progress and its worker) and
|
|
tsc compiles it to `dist/ui/`. Pointing Vite there deletes those modules — the
|
|
CLI then dies at startup with `Cannot find module '../ui/shimmer-progress'` —
|
|
and would also leave the static server handing out compiled engine internals.
|
|
`check-ui-build.mjs` re-asserts the compiled engine is intact after every UI
|
|
build so that mistake cannot land twice.
|
|
|
|
## Layout
|
|
|
|
```
|
|
src/
|
|
main.ts fonts + tokens, mounts App into index.html's #app
|
|
app.css design tokens (light/dark), reset, shell grid
|
|
App.svelte top bar / trail bar / main, global keys
|
|
lib/router.svelte.ts hash router: #/s/<id>, #/file/<path>, #/map, #/flow
|
|
lib/trail.svelte.ts the walked path; mirrored into the `t` query param
|
|
lib/kinds.ts kind glyph letters
|
|
lib/map-model.ts the Map's deterministic layered layout (pure)
|
|
components/ TopBar, TrailBar, KindGlyph, map/, symbol/, file/
|
|
views/ one component per route
|
|
```
|
|
|
|
Fonts (Archivo Variable, IBM Plex Mono) are vendored through `@fontsource*` and
|
|
emitted into `dist/viewer/assets`: a local reader must work offline and must not
|
|
announce the project to a font CDN.
|
|
|
|
## Routes
|
|
|
|
| hash | view |
|
|
|---|---|
|
|
| `#/` | nothing selected |
|
|
| `#/s/<id>?hl=<line>&t=<trail>` | symbol view |
|
|
| `#/file/<path>?hl=<line>` | file view |
|
|
| `#/map?root=&depth=&tests=1` | module map |
|
|
| `#/flow[/<key>]` | flow strip — reserved, phase 2 |
|
|
|
|
Node ids and file paths are encoded per slash-separated segment, so
|
|
`#/file/src/mcp/tools.ts` stays readable and still round-trips a segment
|
|
containing a reserved character. Build hashes with `symbolHref()` /
|
|
`fileHref()` rather than by hand.
|