feat(ui): live refresh and drift banners — the viewer keeps up with the project (CG-53)
`GET /api/events` is a server-sent-event stream the viewer holds open for the life of the page. Two signals, two things the browser could not know: changed source files touched on disk, before any sync — the drift banner index the graph moved, naming what the sync re-indexed — the live refresh The server WATCHES and never syncs: the project tree through the engine's own FileWatcher with a notify-only syncFn, the index through one non-recursive fs.watch on the data directory settled at 400 ms. Both start with the first subscriber and stop with the last, so a viewer nobody has open costs no watch descriptors. Nothing polls, on either side. Drift is now parity with codegraph_node (#1474) rather than an absence. `/api/source?ondrift=current` serves a drifted file's CURRENT bytes flagged `showing: 'current'`, and the three screens that can say so switch off everything anchored to the old line numbering — gutter ports, call-site links, call arcs, the callee rail's anchoring — while keeping the source. The banner is paper-2 with a hairline rule, never amber: amber belongs to the untested badge. Also fixes a stale read this exposed. A long-lived reader holds an LRU of nodes by id that only its own writes invalidate, so `/api/node/<id>` kept answering with a symbol another process's sync had deleted while `/api/search` beside it said it was gone. GraphSession now drops the read caches when the database (or its WAL) has been written, and the Symbol view follows a symbol whose id changed because an edit above it moved its start line, carrying the trail across. Measured on a live viewer: banner 360 ms after a save, toast 440 ms after `codegraph sync` returns, 0 requests in 4 idle seconds, and the client gives up reconnecting after ~90 s with "Not live" rather than hammering a dead port.
This commit is contained in:
@@ -226,6 +226,30 @@ worker entry, a script — ranked by calls × the number of other files they rea
|
||||
depended-on symbols. Each section says what it is derived from, never that a file IS the entry
|
||||
point.
|
||||
|
||||
### 3.8 Drift banner and live refresh (CG-53)
|
||||
Drift banner: full-width block above the code, `--paper-2` fill, 1px `--rule-soft` border, padding `8px 12px`, 12.5px `--ink-2`, leading
|
||||
"⚠" glyph in `--ink-3`. **Never amber** — amber is the untested badge's colour and nothing else's — and never a modal.
|
||||
Toast: `--ink` fill, `--paper` text, 12.5px, `8px 14px`, bottom-centre, 2.6 s, one at a time.
|
||||
|
||||
**As built.** The endpoint is **`/api/events`**, not `/events`: everything under `/api/` answers JSON for every outcome and is
|
||||
excluded from the SPA fallback, so a stream mounted outside that namespace would have come back as the app shell on a typo and as
|
||||
`text/plain` on a refusal. It carries four event types — `hello` (the index revision the client is synchronised against, and which of
|
||||
the two watchers came up), `changed` (source files on disk, before any sync), `index` (the graph moved, naming what the sync
|
||||
re-indexed) and `degraded` — plus a `: ping` comment frame every 25 s. The server WATCHES and never syncs: the project tree through
|
||||
the engine's own `FileWatcher` with a notify-only `syncFn`, the index through one non-recursive `fs.watch` on the data directory
|
||||
settled at 400 ms (capped at 3 s). Both start with the first subscriber and stop with the last.
|
||||
|
||||
Three banner variants, because what follows the dash is what the screen actually did:
|
||||
- **Symbol view** — "indexed line ranges may be shifted; showing the file's current source. The next sync picks it up." The whole
|
||||
CURRENT file replaces the body (parity with `codegraph_node` on a drifted file, issue #1474) and every line-anchored marking goes
|
||||
with the old numbering: gutter ports, call-site links, the definition-name weight, the `?hl=` highlight, and the callee rail's
|
||||
anchoring — its rows stack in source order and draw no connector. Above 400 lines the banner links to the whole-file view instead.
|
||||
- **Whole file (`?src=1`)** — the same, plus "with the call arcs, ports and rail switched off". The source still pages in; only the
|
||||
margins go.
|
||||
- **File outline** — "the outline below is the shape the file had when it was indexed", with a link to the current source.
|
||||
|
||||
Measured: banner 360 ms after a save; toast 440 ms after `codegraph sync` returns; 0 requests in 4 idle seconds.
|
||||
|
||||
## 4. Libraries and versions
|
||||
- Svelte 5 (≥ 5.25) + Vite (workspace `ui/`), Svelte Flow `@xyflow/svelte` ^1.6 for the Map and Flow canvases only (custom nodes/edges,
|
||||
hidden handles for port spreading, local selection state — the pattern in docker-app's `StackGraph.svelte`); `@dagrejs/dagre` only as a
|
||||
@@ -252,7 +276,7 @@ point.
|
||||
## 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",
|
||||
"outside the index", "Where the graph stops", "changed on disk after the last index sync".
|
||||
"outside the index", "Where the graph stops", "changed on disk after the last index sync", "Index updated · reloaded", "Not live".
|
||||
|
||||
---
|
||||
|
||||
|
||||
Reference in New Issue
Block a user