feat(ui): the viewer's screens as @colbymchenry/codegraph-ui, behind one adapter (CG-61)

`ui/src` now builds two ways from one tree: the static app `codegraph ui`
serves, and — via `svelte-package` — a Svelte library the Pro app imports.
A forked component would be a second answer to the same question about the
same graph, so there is no fork.

Everything a screen knows arrives through a `GraphAdapter`: eleven methods
answering the wire shapes verbatim, with `createHttpAdapter()` (the loopback
JSON API) as the default and a host's in-process engine reads as the point.
`lib/api.ts` became a one-line-per-call facade over it, which is why no call
site in the views changed. The payload types moved to `lib/wire.ts` — no
imports, no runtime — so a host can depend on the vocabulary alone.

Two more seams and one guard:

- `lib/navigation.ts` holds the href builders behind a `NavigationDriver`, so
  a host addresses its own URL space. The app's half — the hash parser and the
  live route, which attach window listeners at module scope — stays in
  `router.svelte.ts` and is pruned out of the package: rendering a Symbol view
  must not install a hash router in somebody else's application.
- `lib/theme.css` carries the design tokens and maps Svelte Flow's `--xy-*`
  variables onto them, so a host never sees library defaults. Dark now also
  answers to a bare `[data-theme]`, which is how `<CodegraphUi theme>` themes
  a container rather than the document.
- `scripts/check-ui-package.mjs` prunes the app's shell, resolves the
  extensionless specifiers svelte-package leaves behind, and asserts that
  nothing but `lib/adapter.js` reaches the network.

The search box, its keyboard and its panel are one component now
(`SearchPalette`), because splitting them is what breaks a palette.

`__tests__/ui-package.test.ts` mounts the three screens from the package entry
against a mock adapter in jsdom; it runs as a second vitest project so the
`browser` resolve condition it needs cannot reach the engine's suites.

Versioned with the engine. Prepared, not published: `private: true` is the
guard and `pack-npm.sh` only packs a tarball under CODEGRAPH_PACK_UI=1.
This commit is contained in:
Colby McHenry
2026-08-27 06:52:57 -05:00
parent ad91c8fdd8
commit c15413f200
42 changed files with 4245 additions and 1157 deletions
+21 -1
View File
@@ -334,7 +334,7 @@ with `src/index.ts` selected, 15 links and 4 dimmed boxes, matching the canvas).
roughly double the token count on a dense line.
- The classification is a class NAME, never a colour, and the viewer paints it from the CSS custom properties above — so **one
token stream serves light and dark** with no refetch when `prefers-color-scheme` flips, and the ramp lives only in
`ui/src/app.css`. `type` is a distinct class painted at plain ink: the colouring is near-monochrome and a type name is not one
`ui/src/lib/theme.css`. `type` is a distinct class painted at plain ink: the colouring is near-monochrome and a type name is not one
of the four things it moves off plain ink.
- 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
classifier produced rather than re-cutting a line — which is what keeps a link landing on the callee's own name whatever
@@ -350,6 +350,26 @@ with `src/index.ts` selected, 15 links and 4 dimmed boxes, matching the canvas).
- No native modules; no runtime dependency for the UI itself; the CLI serves **`dist/viewer/`** over `node:http`, loopback only.
(Not `dist/ui/``src/ui/` is the engine's *terminal* ui and tsc already compiles it there; see `ui/README.md`.)
### 4.1 The component library (`@colbymchenry/codegraph-ui`, CG-61)
The same `ui/src` tree builds a second way — `svelte-package` into `ui/dist` — so CodeGraph Pro renders the Symbol view, the Flow
strip and the Map over its own in-process engine reads without forking a component. One tree, because a fork is a second answer to
the same question about the same graph.
- **One seam: `GraphAdapter`** (`ui/src/lib/adapter.ts`) — eleven methods answering the `Wire*` shapes verbatim. `createHttpAdapter()`
is the loopback JSON API and is what the CLI's viewer runs on; a host implements the same methods and never makes a request.
The shapes live in `ui/src/lib/wire.ts`, which has no imports and no runtime, so a host can depend on the vocabulary alone.
`scripts/check-ui-package.mjs` asserts that nothing in the built package but `lib/adapter.js` reaches the network.
- **`events` is optional.** No live channel means nothing connects and nothing polls; a host that learns of a sync some other way
calls `live.signal('index')`, the same code path the stream uses.
- **Navigation is a driver, not a callback** (`ui/src/lib/navigation.ts`): the components build hrefs, because middle-click and
"copy link address" are how people read code. The default is the viewer's hash space; a host installs its own URL space. The
app's half — the hash parser and the live route — attaches window listeners at module scope and is **pruned out of the package**.
- **Theming is colour and type only.** `theme.css` carries the §2.1 tokens and maps Svelte Flow's `--xy-*` variables onto them, so a
host never sees library defaults in the pane, controls or minimap. Geometry (34px rail rows, the 300/320px rails, the 20px code
line) is not themable: the Symbol view measures those against each other to put a callee row beside the line that calls it.
- Versioned with the engine (`scripts/sync-ui-version.mjs`), because the payload shapes are versioned with the binary that serves
them. **Prepared, not published**: `"private": true` is the guard and `scripts/pack-npm.sh` only packs it under
`CODEGRAPH_PACK_UI=1`.
## 5. Copy rules
Sentence case; controls say what happens ("Read as flow", "Clear"); counts always visible next to folds; honesty phrases fixed:
"No test reaches this within 3 caller hops", "Reached by tests · N files within 3 hops", "Uncertain · N name-only matches, confidence < 0.6",