`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.
37 lines
870 B
TypeScript
37 lines
870 B
TypeScript
/**
|
|
* The one transient message the viewer has: "Index updated · reloaded".
|
|
*
|
|
* A note, not a dialog — nothing was asked of the reader and nothing is waiting
|
|
* on them. It replaces itself rather than stacking, because the only thing it
|
|
* ever reports is the most recent state of one fact.
|
|
*/
|
|
|
|
/** How long a note stays up (design spec). */
|
|
export const TOAST_MS = 2_600;
|
|
|
|
let message = $state<string | null>(null);
|
|
let timer: ReturnType<typeof setTimeout> | null = null;
|
|
|
|
function show(text: string): void {
|
|
if (timer !== null) clearTimeout(timer);
|
|
message = text;
|
|
timer = setTimeout(() => {
|
|
message = null;
|
|
timer = null;
|
|
}, TOAST_MS);
|
|
}
|
|
|
|
function clear(): void {
|
|
if (timer !== null) clearTimeout(timer);
|
|
timer = null;
|
|
message = null;
|
|
}
|
|
|
|
export const toast = {
|
|
get message(): string | null {
|
|
return message;
|
|
},
|
|
show,
|
|
clear,
|
|
};
|