diff --git a/apps/web/src/App.tsx b/apps/web/src/App.tsx index 826c1a3..fdb610e 100644 --- a/apps/web/src/App.tsx +++ b/apps/web/src/App.tsx @@ -17,7 +17,6 @@ import { createDashboardRequestAborter, runViewportAwareDashboardHydrationQueue, shouldPauseDashboardRefresh, - subscribeToDashboardTileEvents, waitForDashboardHydrationIdle, type DashboardIntersectionObserverFactory, type DashboardTileReference, @@ -178,7 +177,6 @@ export default function App() { useEffect(() => { let cancelled = false; let refreshTimer: number | undefined; - let unsubscribeTileEvents: (() => void) | undefined; let lastRefreshIntervalMs = dashboardFallbackRefreshIntervalMs; let hydrationRun = 0; const requestAborter = createDashboardRequestAborter(); @@ -215,8 +213,6 @@ export default function App() { function pauseRefreshes() { clearRefreshTimer(); - unsubscribeTileEvents?.(); - unsubscribeTileEvents = undefined; requestAborter.abortActiveRequests(); setHydratingItemIds(new Set()); } @@ -270,14 +266,6 @@ export default function App() { schemaVersion: restored.dashboard.schemaVersion, }, ); - subscribeDashboardTileEvents( - currentRun, - tileSignal, - { - currentRevisionId: restored.dashboard.currentRevisionId, - schemaVersion: restored.dashboard.schemaVersion, - }, - ); } else { setHydratingItemIds(new Set()); } @@ -370,30 +358,6 @@ export default function App() { }); } - function subscribeDashboardTileEvents( - run: number, - signal: AbortSignal, - snapshotContext: DashboardTileSnapshotStoreContext, - ) { - unsubscribeTileEvents?.(); - unsubscribeTileEvents = subscribeToDashboardTileEvents({ - onTile: (event) => { - const tileResponse = event as DashboardTileResponse; - if (!isDashboardTileResponse(tileResponse)) return; - applyDashboardTileHydrationResponse( - tileResponse.tile, - tileResponse, - run, - signal, - snapshotContext, - ); - }, - onUnavailable: () => { - unsubscribeTileEvents = undefined; - }, - }); - } - async function hydrateDashboardTileBatch( tiles: DashboardTileReference[], run: number, @@ -562,7 +526,6 @@ export default function App() { return () => { cancelled = true; clearRefreshTimer(); - unsubscribeTileEvents?.(); requestAborter.abortActiveRequests(); detachRefreshLifecycle(); }; @@ -662,20 +625,6 @@ function isAbortError(error: unknown): boolean { ); } -function isDashboardTileResponse(value: unknown): value is DashboardTileResponse { - return ( - typeof value === "object" && - value !== null && - "state" in value && - (value.state === "ready" || - value.state === "not_found" || - value.state === "disabled") && - "tile" in value && - typeof value.tile === "object" && - value.tile !== null - ); -} - export function restoreDashboardTileSnapshots( dashboard: DashboardRuntimeState, snapshots: RestoredDashboardTileSnapshot[], diff --git a/apps/web/src/lib/client/dashboard-refresh.test.ts b/apps/web/src/lib/client/dashboard-refresh.test.ts index 7da37ea..e17eab2 100644 --- a/apps/web/src/lib/client/dashboard-refresh.test.ts +++ b/apps/web/src/lib/client/dashboard-refresh.test.ts @@ -13,7 +13,6 @@ import { runViewportAwareDashboardHydrationQueue, shouldPauseDashboardRefresh, splitDashboardHydrationItemsByVisibility, - subscribeToDashboardTileEvents, waitForDashboardHydrationIdle, type DashboardIntersectionEntry, } from "./dashboard-refresh"; @@ -303,69 +302,6 @@ describe("dashboard refresh lifecycle", () => { ]); }); - test("subscribes to dashboard tile events", () => { - const received: unknown[] = []; - let listener: ((event: MessageEvent) => void) | undefined; - let closed = false; - - const unsubscribe = subscribeToDashboardTileEvents({ - createEventSource: (url) => { - expect(url).toBe("/api/dashboard/events"); - return { - addEventListener(_type, eventListener) { - listener = eventListener; - }, - close() { - closed = true; - }, - onerror: null, - }; - }, - onTile: (tile) => received.push(tile), - }); - - listener?.({ data: JSON.stringify({ state: "ready" }) } as MessageEvent); - expect(received).toEqual([{ state: "ready" }]); - unsubscribe(); - expect(closed).toBe(true); - }); - - test("falls back when dashboard tile events are unavailable or fail", () => { - let unavailableCount = 0; - - subscribeToDashboardTileEvents({ - createEventSource: undefined, - onTile: () => undefined, - onUnavailable: () => { - unavailableCount += 1; - }, - }); - - let errorHandler: (() => void) | null = null; - const unsubscribe = subscribeToDashboardTileEvents({ - createEventSource: () => ({ - addEventListener: () => undefined, - close: () => undefined, - get onerror() { - return errorHandler; - }, - set onerror(handler) { - errorHandler = handler; - }, - }), - onTile: () => undefined, - onUnavailable: () => { - unavailableCount += 1; - }, - }); - - if (!errorHandler) throw new Error("expected error handler"); - const triggerError = errorHandler as unknown as () => void; - triggerError(); - unsubscribe(); - expect(unavailableCount).toBe(2); - }); - test("backs off failed tile keys and resets after success", () => { const backoff = createDashboardTileBackoff(); diff --git a/apps/web/src/lib/client/dashboard-refresh.ts b/apps/web/src/lib/client/dashboard-refresh.ts index 989816c..76cc83d 100644 --- a/apps/web/src/lib/client/dashboard-refresh.ts +++ b/apps/web/src/lib/client/dashboard-refresh.ts @@ -489,51 +489,6 @@ export function createDashboardPerformanceMarks( }; } -export interface DashboardTileEventSource { - addEventListener( - type: "dashboard-tile", - listener: (event: MessageEvent) => void, - ): void; - close(): void; - onerror: (() => void) | null; -} - -export interface DashboardTileEventSubscriptionOptions { - createEventSource?: (url: string) => DashboardTileEventSource; - onTile: (data: unknown) => void; - onUnavailable?: () => void; - url?: string; -} - -export function subscribeToDashboardTileEvents( - options: DashboardTileEventSubscriptionOptions, -): () => void { - const createEventSource = options.createEventSource || - (typeof globalThis.EventSource !== "undefined" - ? (url: string) => new globalThis.EventSource(url) - : undefined); - - if (!createEventSource) { - options.onUnavailable?.(); - return () => undefined; - } - - const source = createEventSource(options.url || "/api/dashboard/events"); - source.addEventListener("dashboard-tile", (event) => { - try { - options.onTile(JSON.parse(event.data)); - } catch { - // Ignore malformed diagnostics from an optional live transport. - } - }); - source.onerror = () => { - source.close(); - options.onUnavailable?.(); - }; - - return () => source.close(); -} - const dashboardTileBackoffDelaysMs = [15_000, 30_000, 60_000, 120_000]; export function createDashboardTileBackoff() { diff --git a/apps/web/src/server/index.test.ts b/apps/web/src/server/index.test.ts index af1035d..9e5a34d 100644 --- a/apps/web/src/server/index.test.ts +++ b/apps/web/src/server/index.test.ts @@ -50,28 +50,4 @@ describe("server request routing", () => { expect(response.status).toBe(405); expect(response.headers.get("allow")).toBe("POST"); }); - - test("routes dashboard event stream requests", async () => { - const response = await handleRequest( - new Request("https://example.test/api/dashboard/events", { - method: "GET", - }), - ); - - expect(response.status).toBe(200); - expect(response.headers.get("content-type")).toBe( - "text/event-stream; charset=utf-8", - ); - }); - - test("rejects non-get dashboard event stream requests", async () => { - const response = await handleRequest( - new Request("https://example.test/api/dashboard/events", { - method: "POST", - }), - ); - - expect(response.status).toBe(405); - expect(response.headers.get("allow")).toBe("GET"); - }); }); diff --git a/apps/web/src/server/index.ts b/apps/web/src/server/index.ts index 9cbcbd9..cf624ed 100644 --- a/apps/web/src/server/index.ts +++ b/apps/web/src/server/index.ts @@ -1,7 +1,6 @@ import { extname, normalize } from "node:path"; import { handleAgentDashboardRoute } from "./routes/agent-dashboard"; import { - handleDashboardEventsRoute, handleDashboardRoute, handleDashboardTileRoute, handleDashboardTilesRoute, @@ -33,11 +32,6 @@ export async function handleRequest(request: Request): Promise { return handleDashboardTilesRoute(request); } - if (url.pathname === "/api/dashboard/events") { - if (request.method !== "GET") return methodNotAllowed(["GET"]); - return handleDashboardEventsRoute({ signal: request.signal }); - } - if (url.pathname.startsWith("/api/dashboard/tile/")) { if (request.method !== "GET") return methodNotAllowed(["GET"]); return handleDashboardTileRoute(url.pathname); diff --git a/apps/web/src/server/routes/dashboard.test.ts b/apps/web/src/server/routes/dashboard.test.ts index a4cf7ef..a0054e2 100644 --- a/apps/web/src/server/routes/dashboard.test.ts +++ b/apps/web/src/server/routes/dashboard.test.ts @@ -3,7 +3,6 @@ import { dimensionLabDashboardFixture } from "$lib/dashboard-seed/dimensionlab"; import { createDashboardTileCache, dashboardTileCacheKey, - handleDashboardEventsRoute, handleDashboardTilesRoute, handleDashboardTileRoute, loadDashboardResponse, @@ -606,45 +605,6 @@ describe("dashboard API route", () => { }); }); - test("streams ready dashboard tile events", async () => { - const controller = new AbortController(); - const response = await handleDashboardEventsRoute({ - refreshSeedDocument: true, - seedIfEmpty: true, - signal: controller.signal, - tileCache: { - async resolve(key) { - controller.abort(); - const tile = JSON.parse(key); - - return { - cache: "miss", - coalesced: false, - response: { - state: "ready", - tile, - item: { - id: tile.id, - label: "Status", - severity: "ok", - value: "ok", - }, - }, - }; - }, - }, - }); - - expect(response.headers.get("content-type")).toBe( - "text/event-stream; charset=utf-8", - ); - expect(response.headers.get("cache-control")).toBe("no-cache"); - const body = await response.text(); - expect(body).toContain("event: dashboard-tile"); - expect(body).toContain('"state":"ready"'); - expect(body).toContain('"tile"'); - }); - test("rejects invalid batch tile requests", async () => { const response = await handleDashboardTilesRoute( new Request("https://example.test/api/dashboard/tiles", { diff --git a/apps/web/src/server/routes/dashboard.ts b/apps/web/src/server/routes/dashboard.ts index c42227c..2917689 100644 --- a/apps/web/src/server/routes/dashboard.ts +++ b/apps/web/src/server/routes/dashboard.ts @@ -25,10 +25,6 @@ export interface LoadDashboardResponseOptions tileCache?: DashboardTileCache; } -export interface DashboardEventsRouteOptions extends LoadDashboardResponseOptions { - signal?: AbortSignal; -} - interface DashboardTileCacheEntry { expiresAt: number; response: DashboardTileResolution; @@ -297,48 +293,6 @@ export async function handleDashboardTilesRoute( }); } -export async function handleDashboardEventsRoute( - options: DashboardEventsRouteOptions = {}, -): Promise { - const stream = new ReadableStream({ - async start(controller) { - const encoder = new TextEncoder(); - const dashboard = await loadDashboardResponse({ - ...options, - hydrateLiveDatasources: false, - }); - - if (dashboard.state !== "ready" || options.signal?.aborted) { - controller.close(); - return; - } - - try { - for (const tile of dashboardEventTiles(dashboard.document)) { - if (options.signal?.aborted) break; - const resolution = await loadDashboardTileResponse(tile, options); - if (resolution.state === "ready") { - controller.enqueue( - encoder.encode(dashboardTileEventChunk(resolution)), - ); - } - } - } finally { - controller.close(); - } - }, - }); - - return new Response(stream, { - headers: { - "Cache-Control": "no-cache", - "Connection": "keep-alive", - "Content-Type": "text/event-stream; charset=utf-8", - "X-Accel-Buffering": "no", - }, - }); -} - export function dashboardTileCacheKey(tile: DashboardTileReference): string { return JSON.stringify(tile); } @@ -468,45 +422,6 @@ function parseDashboardTileReference(value: unknown): DashboardTileReference | n return null; } -function dashboardEventTiles(document: DashboardDocument): DashboardTileReference[] { - const status = document.statusStrips.flatMap((strip) => - strip.items.map((item): DashboardTileReference => ({ - kind: "status", - stripId: strip.id, - id: item.id, - })), - ); - const telemetry = document.telemetry - .filter((card) => card.datasource?.type === "external") - .map((card): DashboardTileReference => ({ kind: "telemetry", id: card.id })); - const modules = (document.modules || []) - .filter((module) => - module.datasource?.type === "external" || - module.id === "runtime-health-summary" - ) - .map((module): DashboardTileReference => ({ kind: "module", id: module.id })); - const services = document.serviceGroups.flatMap((group) => - group.services - .filter((service) => service.datasource?.type === "external") - .map((service): DashboardTileReference => ({ - kind: "service", - groupId: group.id, - id: service.id, - })), - ); - - return [...status, ...telemetry, ...modules, ...services]; -} - -function dashboardTileEventChunk(resolution: DashboardTileResolution): string { - return [ - "event: dashboard-tile", - `data: ${JSON.stringify(resolution)}`, - "", - "", - ].join("\n"); -} - class DashboardTileCacheResolutionError extends Error { readonly cache: "hit" | "miss"; readonly coalesced: boolean;