Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
97 changes: 0 additions & 97 deletions crates/codegraph-ui/viewer/assets/index-Bpmr0I56.js

This file was deleted.

1 change: 1 addition & 0 deletions crates/codegraph-ui/viewer/assets/index-CQkoLDwl.css

Large diffs are not rendered by default.

1 change: 0 additions & 1 deletion crates/codegraph-ui/viewer/assets/index-CfvuSGC9.css

This file was deleted.

94 changes: 94 additions & 0 deletions crates/codegraph-ui/viewer/assets/index-Cvst5APO.js

Large diffs are not rendered by default.

Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
4 changes: 2 additions & 2 deletions crates/codegraph-ui/viewer/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@
<title>CodeGraph</title>
<!-- Inline, so a loopback server never has to answer a favicon request. -->
<link rel="icon" href="data:," />
<script type="module" crossorigin src="./assets/index-Bpmr0I56.js"></script>
<link rel="stylesheet" crossorigin href="./assets/index-CfvuSGC9.css">
<script type="module" crossorigin src="./assets/index-Cvst5APO.js"></script>
<link rel="stylesheet" crossorigin href="./assets/index-CQkoLDwl.css">
</head>
<body>
<div id="app"></div>
Expand Down
63 changes: 60 additions & 3 deletions docs/ui.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@ outline and imports, the call path between two symbols, the repository as a map
of modules, a type's hierarchy, the code nothing reaches, and the places to start
reading an unfamiliar project. It is a Rust port of the upstream
colbymchenry/codegraph `v1.6.1` viewer: the frontend is upstream's `ui/` (Svelte
5 + Vite, MIT, see [`ui/LICENSE`](../ui/LICENSE)) and the JSON API behind it is
the `codegraph-ui` crate.
5 + Vite, MIT, see [`ui/LICENSE`](../ui/LICENSE)), restyled to the selected
design ([`design/viewer-d.md`](design/viewer-d.md), direction D) with a dark and
a light theme, and the JSON API behind it is the `codegraph-ui` crate.

The viewer is a **preview**, gated exactly as upstream gates it: unless
`CODEGRAPH_UI=1` is set, `ui`, its alias `web`, `help ui` and `ui --help` are
Expand Down Expand Up @@ -142,10 +143,66 @@ the Rust graph holds different facts, the viewer shows what this index holds:
- The bundle is embedded in the binary, so upstream's `CODEGRAPH_VIEWER_PATH`
has no counterpart.

## Look and themes

The viewer is drawn in direction D of [`design/viewer-d.md`](design/viewer-d.md):
an icon nav rail on the left, a command bar with the `⌘K` / `Ctrl+K` search
palette, the trail ribbon under it while a trail exists, and each view as
rounded islands 8 px apart. Below 1024 px wide the rail narrows and the Symbol
view's Called by rail becomes a drawer behind a header button; below 600 px a
bottom tab bar (Start, Map, Symbol, Flow, More) replaces the rail, the Symbol
view shows one pane at a time (Code, Called by, Calls), and tapping a call in
the code opens a sheet with that line's calls.

There are two themes with the same token names: **Nebula** (dark, §3.1 of the
spec) and **Daylight** (light, §3.6). The viewer follows the operating system's
`prefers-color-scheme` until the reader picks one: the theme control — System,
Dark, Light — sits in the nav rail's foot on desktop and tablet and in the More
sheet on the phone. The choice is stored in the browser's `localStorage` under
`codegraph-ui.theme` and applied as `data-theme` on the page; System removes
it. Copy image and Download SVG on the Flow and Map views paint the theme on
screen. `ui/tests/theme-contrast.test.ts` reads both token sets out of
`ui/src/lib/theme.css` and fails if any pair the spec declares falls below
4.5:1.

Interface text is set in Inter, code and names in JetBrains Mono, both bundled
(OFL) so the viewer never fetches a font; ligatures are off, so `->` reads as
written. The icons are Lucide (ISC) geometry, copied into
`ui/src/lib/icons.ts` with its notice.

### Direction D as built — 偏离(画板 vs 落地,供 owner 复核)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Keep the canonical heading in English

docs/ui.md is the canonical technical reference, but this heading includes Chinese text; replace it with an English-only heading so the page follows the repository's explicit canonical-documentation language rule.

AGENTS.md reference: docs/AGENTS.md:L5-L6

Useful? React with 👍 / 👎.


| item | board | landed |
| ------------------------ | ---------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| project switch | name, chevron and a `main · <sha>` branch pill | the name alone: one project per viewer, and the API carries no git branch |
| rail foot | keyboard and settings | the theme switch above them; keyboard opens the shortcut sheet, settings a panel of what this viewer reads (port, read-only and watching are command-line choices) |
| Saved trails destination | a trails screen | opens Entry points, which lists saved trails first |
| Start, entry card | "Entry functions" with Flow buttons | "Where it starts" — routes when the project is routed, else the files that run something at their top level — opening the row; a flow starts from Entry points |
| Flow | 190 × 168 step cards and a step-detail panel | upstream's cards opened at each call window, restyled; Every hop table below; its Runs when column reads "—" until conditions are ported |
| Map toolbar | zoom readout and an Include tests switch | the zoom controls; Include tests lives in the inspector's View section; Open crate is Open first file |
| Map key | the one-line legend | the same, opening to upstream's full key on request (upstream opened the full key by default) |
| File, This file | edges in and out, prod and test files | how many files depend on it and it on them, its symbols, its size — no edge totals in the payload |
| hierarchy | a supertrait outside the index drawn at 50 % | not drawn: the payload carries no outside-index supertypes |
| code lines | a long line ends in `…` | a long line scrolls sideways, as upstream's did |
| trail hops | qualified names | the symbol's name, as upstream labels a hop |
| SVG export | — (upstream: always light) | the theme on screen |
| ligatures | the boards show `->` as an arrow | off (§13 left it open; source fidelity decides) |
| tablet Symbol | blast radius as two stat tiles under the code | the four tiles stay at the foot of the Called by drawer |

Layout constants the components measure against moved with the design; the
suites pin them by name: hierarchy rows 24 → 26 and indent 22 → 28; file
outline rows at a 30 px pitch; map nodes 40 → 52 high, layers 74 → 44 and boxes
34 → 28 apart; callee rows 34 → 44 high with a 52 px pitch. Character advances
were re-measured in Chrome against the bundled fonts: the map label (12 px
JetBrains Mono 500) 7.81 → 7.2, the map meta line (11 px Inter) 5.9 → 5.5, the
flow link label (11 px mono) 6.65 → 6.6, the Screens pill (10.5 px mono) 6.3,
unchanged.

## Developing the frontend

The frontend lives in `ui/` with its own pinned dependencies and a committed
`ui/package-lock.json`; `npm ci` is the only install path. `npm run build` writes
`ui/package-lock.json`; `npm ci` is the only install path. `npm run build` (or
`make ui`, which runs `npm ci` first) writes
the production bundle into `crates/codegraph-ui/viewer/`, which the crate's
`build.rs` embeds at compile time, so `cargo build` and `cargo install --git`
never need Node. The bundle is committed, and `make ui-check` (the CI `UI` job)
Expand Down
15 changes: 15 additions & 0 deletions docs/upstream-sync/UPSTREAM.md
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,21 @@ below remain immutable historical evidence.

## Sync log

### 2026-10-03 — browser viewer RESTYLED: direction D, a local design layer

The ported viewer's frontend now carries the design the owner selected
([`../design/viewer-d.md`](../design/viewer-d.md), direction D) on top of the
`v1.6.1` import: tokens, type, shell, view layouts, states, responsive layouts
and a second, light token set (§3.6). This is codegraph-rs's own design, not an
upstream change: the wire contract, the routes and the server are untouched, and
the frontend's models keep upstream's behaviour — only the layout constants the
components measure against moved, each pinned by name in the suites.

A later sync of upstream `ui/` therefore re-applies this layer rather than
taking upstream's paper/ink look. The visual differences from the boards, and
from upstream where they matter, are listed in [`../ui.md`](../ui.md) under
"Direction D as built".

### 2026-10-02 — browser viewer PORTED: UI family phase 1 (`codegraph ui`, `CODEGRAPH_UI=1`)

The viewer upstream ships in `v1.6.1` — `src/ui-server/**` (the loopback JSON
Expand Down
32 changes: 21 additions & 11 deletions ui/README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
# ui/ — the `codegraph ui` viewer, and `@colbymchenry/codegraph-ui`

> **In codegraph-rs.** This tree was imported from upstream colbymchenry/codegraph
> `v1.6.1` and is built here by `make ui` into `crates/codegraph-ui/viewer/`,
> which the Rust binary embeds — see [`../docs/ui.md`](../docs/ui.md), the
> canonical reference. Its look is direction D
> ([`../docs/design/viewer-d.md`](../docs/design/viewer-d.md)) with a dark and a
> light theme. The build, packaging and release passages below describe
> upstream's npm monorepo, not this repository.

One source tree, two builds.

- **The app** — the browser reader for an indexed project: Svelte 5 + Vite,
Expand All @@ -16,8 +24,9 @@ each other in a review.
An npm workspace of the engine, so `npm ci` at the repo root installs the
toolchain for both.

Design spec (every token, size and measurement):
`../docs/design/codegraph-ui-design-spec.md`.
Design spec (every token, size and measurement): upstream's
`docs/design/codegraph-ui-design-spec.md`; in codegraph-rs,
[`../docs/design/viewer-d.md`](../docs/design/viewer-d.md).

## Build

Expand Down Expand Up @@ -133,7 +142,7 @@ the screen is explained rather than silently missing.
context, so one page reads one project. `<CodegraphUi>` installs them during
initialisation, once — swapping projects means re-mounting the subtree
(`{#key project}`), not swapping the prop.
3. **Geometry is not themable.** 34px rail rows, the 300/320px rails, the 20px
3. **Geometry is not themable.** 44px callee rows, the 288/320px rails, the 20px
code line: the Symbol view measures these against each other to put a callee
row beside the line that calls it. Colour and type are yours.

Expand Down Expand Up @@ -191,21 +200,22 @@ src/
lib/export-image.ts rasterising that SVG to PNG, clipboard and download
lib/live.svelte.ts /api/events: two counters every screen refreshes from
lib/toast.svelte.ts the one transient note ("Index updated · reloaded")
components/ TopBar, TrailBar, SavedTrails, KindGlyph, DriftBanner, Toast, ExportButtons, map/, flow/, symbol/, file/, entry/, screens/
components/ NavRail, TopBar (the command bar), TrailBar, PhoneTabBar, MoreSheet, SavedTrails, KindGlyph, Icon, DriftBanner, ErrorCard, Toast, ExportButtons, map/, flow/, symbol/, file/, entry/, screens/
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.
Fonts (Inter and JetBrains Mono, both variable) are vendored through
`@fontsource-variable` and emitted with the bundle's assets — never inlined, so
the server's `font-src 'self'` policy holds: a local reader must work offline
and must not announce the project to a font CDN.

## Export

The Flow strip's header and the Map's side panel carry **Copy image** (a PNG on
the clipboard) and **Download SVG** (a file for a README). Both render the
**light** theme whatever the viewer is set to — an image is read on somebody
else's screen — with 24px of paper around the drawing, a caption naming the path
or the root, and a "CodeGraph" mark in the corner.
theme on screen (light when the exporter is called without one), with 24px of
paper around the drawing, a caption naming the path or the root, and a
"CodeGraph" mark in the corner.

`export-svg.ts` **serialises the layout object**; it does not scrape the DOM.
`buildFlowLayout` and `buildMapLayout` already compute every rectangle, port and
Expand All @@ -217,7 +227,7 @@ will render in a README.

Fonts travel as `font-family` stacks rather than embedded bytes. An SVG loaded
as an image may not fetch a webfont, so a raster falls back to the platform's
own monospace; every fallback in the stack advances at ~0.6em like IBM Plex
own monospace; every fallback in the stack advances at ~0.6em like JetBrains
Mono, so the code grid survives and only the letterforms change.

## Routes
Expand Down
16 changes: 8 additions & 8 deletions ui/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@
"@xyflow/svelte": "1.6.5"
},
"devDependencies": {
"@fontsource-variable/archivo": "5.3.0",
"@fontsource/ibm-plex-mono": "5.3.0",
"@fontsource-variable/inter": "5.3.0",
"@fontsource-variable/jetbrains-mono": "5.3.0",
"@sveltejs/vite-plugin-svelte": "6.2.4",
"@types/node": "20.19.33",
"jsdom": "25.0.1",
Expand Down
42 changes: 40 additions & 2 deletions ui/src/App.svelte
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
<script lang="ts">
import { untrack } from 'svelte';
import TopBar from './components/TopBar.svelte';
import NavRail from './components/NavRail.svelte';
import PhoneTabBar from './components/PhoneTabBar.svelte';
import MoreSheet from './components/MoreSheet.svelte';
import TrailBar from './components/TrailBar.svelte';
import HomeView from './views/HomeView.svelte';
import SymbolView from './views/SymbolView.svelte';
Expand Down Expand Up @@ -30,6 +33,7 @@
import { project } from './lib/project.svelte';
import { live } from './lib/live.svelte';
import { toast } from './lib/toast.svelte';
import { command } from './lib/command.svelte';

// One `/api/stats` for the whole app: the top bar's counts and the Symbol
// view's blast-radius denominator come out of the same payload.
Expand Down Expand Up @@ -67,6 +71,25 @@

let topbar: TopBar | null = $state(null);

// A view asked for the palette (the Start screen's Search button).
let seenSearchTick = command.searchTick;
$effect(() => {
const tick = command.searchTick;
untrack(() => {
if (tick === seenSearchTick) return;
seenSearchTick = tick;
topbar?.focusSearch();
});
});

/** The phone's More sheet — opened from the tab bar or the top bar. */
let moreOpen = $state(false);
// Moving on closes it: a destination in the sheet is a navigation.
$effect(() => {
void router.route;
untrack(() => (moreOpen = false));
});

let route = $derived(router.route);

// An app with screens opens on them. The Symbol tab's empty state is for a
Expand Down Expand Up @@ -155,7 +178,8 @@

<svelte:window {onkeydown} />

<TopBar bind:this={topbar} project={project.name} stats={project.summary} showScreens={hasScreens} />
<NavRail {hasScreens} />
<TopBar bind:this={topbar} project={project.name} onmore={() => (moreOpen = true)} />
<TrailBar />
<main>
{#if route.view === 'symbol' && route.id !== null}
Expand Down Expand Up @@ -189,15 +213,29 @@
<HomeView project={project.name} />
{/if}
</main>
<PhoneTabBar {hasScreens} {moreOpen} onmore={() => (moreOpen = !moreOpen)} />
{#if moreOpen}
<MoreSheet {hasScreens} onclose={() => (moreOpen = false)} />
{/if}
<Toast />

<style>
/* The shell grid lives on #app (index.html's mount host) in app.css —
Svelte's scoped styles cannot reach an element this component does not
render. Only <main>, which it does render, is styled here. */
main {
/* min-height:0 lets the row shrink so the view, not the page, scrolls. */
grid-area: main;
/* min-height:0 lets the row shrink so the view, not the page, scrolls.
The islands sit 8px from the ribbon (or the bar), the right edge and
the bottom (§2: x 72 … W−8, y 104 … H−8). */
min-height: 0;
overflow: hidden;
padding: var(--gap) var(--gap) var(--gap) var(--gap);
}

@media (max-width: 599px) {
main {
padding: var(--gap);
}
}
</style>
Loading