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:
+26
-1
@@ -45,7 +45,9 @@ src/
|
||||
lib/map-model.ts the Map's deterministic layered layout (pure)
|
||||
lib/flow-model.ts the Flow strip's card/link geometry — a DAG (pure)
|
||||
lib/filecode-model.ts the whole-file view: fixed line height, arcs, paging (pure)
|
||||
components/ TopBar, TrailBar, KindGlyph, map/, flow/, symbol/, file/
|
||||
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, KindGlyph, DriftBanner, Toast, map/, flow/, symbol/, file/
|
||||
views/ one component per route
|
||||
```
|
||||
|
||||
@@ -66,6 +68,29 @@ announce the project to a font CDN.
|
||||
| `#/flow?symbols=a,b,c` | flow strip — `codegraph_explore`'s own question |
|
||||
| `#/flow?t=<trail>` | flow strip — the trail you walked, read as a flow |
|
||||
|
||||
## Live updates
|
||||
|
||||
The viewer never polls. `lib/live.svelte.ts` holds one `EventSource` on
|
||||
`/api/events` for the life of the page and exposes two counters:
|
||||
|
||||
- **`indexTick`** — the graph moved (somebody synced). Every screen refetches:
|
||||
a rail is an answer about the whole graph, and a symbol gains a caller when
|
||||
some *other* file is edited, so filtering by the focused file would leave the
|
||||
rails quietly wrong. One request per sync.
|
||||
- **`diskTick`** — source files changed on disk and the index has not caught up.
|
||||
Only the screen showing one of those files reacts, and what it does is draw a
|
||||
drift banner.
|
||||
|
||||
`liveRefresh(file, refresh)` is the three lines of bookkeeping that turns a
|
||||
counter into a single call; the Map and the Flow strip instead read
|
||||
`live.indexTick` straight inside the effect that already fetches them.
|
||||
|
||||
Reconnection is ours, not `EventSource`'s: each failure closes the stream and
|
||||
schedules ONE retry on a backoff that ends after eight attempts (~90 s), at
|
||||
which point the top bar says "Not live" and nothing more is requested until the
|
||||
tab is focused again. A `degraded` event — the server's watcher gave up — is
|
||||
shown the same way and never answered with a poll.
|
||||
|
||||
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()` /
|
||||
|
||||
Reference in New Issue
Block a user