diff --git a/apps/web/src/App.tsx b/apps/web/src/App.tsx index ac3b31b..16e5c5e 100644 --- a/apps/web/src/App.tsx +++ b/apps/web/src/App.tsx @@ -10,7 +10,6 @@ import type { DashboardRuntimeState } from "$lib/server/dashboard"; import { attachDashboardRefreshLifecycle, collectVisibleDashboardModelIds, - createDashboardPerformanceMarks, createDashboardRefreshDelay, createDashboardTileSnapshotStore, createDashboardTileBackoff, @@ -175,7 +174,6 @@ export default function App() { let lastRefreshIntervalMs = dashboardFallbackRefreshIntervalMs; let hydrationRun = 0; const requestAborter = createDashboardRequestAborter(); - const performanceMarks = createDashboardPerformanceMarks(); const refreshDelay = createDashboardRefreshDelay(); const tileBackoff = createDashboardTileBackoff(); const tileSnapshotStore = createDashboardTileSnapshotStore( @@ -224,7 +222,6 @@ export default function App() { if (cancelled || shellSignal.aborted || refreshPaused()) return; refreshDelay.recordSuccess(); - performanceMarks.markShellLoad(); const restored = restoreDashboardTileSnapshots( nextDashboard, nextDashboard.state === "ready" @@ -322,8 +319,6 @@ export default function App() { } }, items: tiles, - onAllItemsSettled: () => performanceMarks.markAllTilesSettled(), - onVisibleItemsSettled: () => performanceMarks.markVisibleTilesReady(), signal, waitForIdle: () => waitForDashboardHydrationIdle({ @@ -372,7 +367,6 @@ export default function App() { ...snapshotContext, response: readyTileResponse, }); - performanceMarks.markFirstTileReady(); return "ready"; } catch (error) { if (!isAbortError(error) && !cancelled) { diff --git a/apps/web/src/lib/client/dashboard-refresh.test.ts b/apps/web/src/lib/client/dashboard-refresh.test.ts index 2a2f25d..d94b0c4 100644 --- a/apps/web/src/lib/client/dashboard-refresh.test.ts +++ b/apps/web/src/lib/client/dashboard-refresh.test.ts @@ -2,9 +2,7 @@ import { describe, expect, test } from "vitest"; import { attachDashboardRefreshLifecycle, collectVisibleDashboardModelIds, - createDashboardPerformanceMarks, createDashboardRefreshDelay, - dashboardPerformanceMarks, createDashboardTileSnapshotStore, createDashboardTileBackoff, createDashboardRequestAborter, @@ -133,8 +131,6 @@ describe("dashboard refresh lifecycle", () => { calls.push(item); }, items: ["a", "b", "c"], - onAllItemsSettled: () => calls.push("all-settled"), - onVisibleItemsSettled: () => calls.push("visible-settled"), signal: new AbortController().signal, waitForIdle: async () => { calls.push("idle"); @@ -143,18 +139,11 @@ describe("dashboard refresh lifecycle", () => { }); await waitFor(() => calls.includes("idle")); - expect(calls).toEqual(["b", "visible-settled", "idle"]); + expect(calls).toEqual(["b", "idle"]); idleReleases.shift()?.(); await hydration; - expect(calls).toEqual([ - "b", - "visible-settled", - "idle", - "a", - "c", - "all-settled", - ]); + expect(calls).toEqual(["b", "idle", "a", "c"]); }); test("splits visible hydration items while preserving document order", () => { @@ -258,30 +247,6 @@ describe("dashboard refresh lifecycle", () => { expect(delay.nextDelayMs(1_000)).toBe(1_100); }); - test("marks dashboard performance milestones once per shell run", () => { - const marks: string[] = []; - const performanceMarks = createDashboardPerformanceMarks({ - mark: (name) => marks.push(name), - }); - - performanceMarks.markShellLoad(); - performanceMarks.markFirstTileReady(); - performanceMarks.markFirstTileReady(); - performanceMarks.markVisibleTilesReady(); - performanceMarks.markAllTilesSettled(); - performanceMarks.markShellLoad(); - performanceMarks.markFirstTileReady(); - - expect(marks).toEqual([ - dashboardPerformanceMarks.shellLoad, - dashboardPerformanceMarks.firstTileReady, - dashboardPerformanceMarks.visibleTilesReady, - dashboardPerformanceMarks.allTilesSettled, - dashboardPerformanceMarks.shellLoad, - dashboardPerformanceMarks.firstTileReady, - ]); - }); - 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 a9aa3a5..a77fdd1 100644 --- a/apps/web/src/lib/client/dashboard-refresh.ts +++ b/apps/web/src/lib/client/dashboard-refresh.ts @@ -157,8 +157,6 @@ export interface DashboardViewportHydrationQueueOptions { getModelId: (item: TItem) => string; hydrate: (item: TItem) => Promise | void; items: TItem[]; - onAllItemsSettled?: () => void; - onVisibleItemsSettled?: () => void; signal: AbortSignal; waitForIdle: () => Promise; } @@ -181,13 +179,8 @@ export async function runViewportAwareDashboardHydrationQueue( items: visible, signal: options.signal, }); - if (options.signal.aborted) return; - options.onVisibleItemsSettled?.(); - if (!deferred.length) { - options.onAllItemsSettled?.(); - return; - } + if (!deferred.length || options.signal.aborted) return; await options.waitForIdle(); if (options.signal.aborted) return; @@ -198,7 +191,6 @@ export async function runViewportAwareDashboardHydrationQueue( items: deferred, signal: options.signal, }); - options.onAllItemsSettled?.(); } export function splitDashboardHydrationItemsByVisibility(options: { @@ -416,51 +408,6 @@ export function createDashboardRefreshDelay( }; } -export interface DashboardPerformanceMarkOptions { - mark?: (name: string) => void; -} - -export const dashboardPerformanceMarks = { - allTilesSettled: "dashboard:all-tiles-settled", - firstTileReady: "dashboard:first-tile-ready", - shellLoad: "dashboard:shell-load", - visibleTilesReady: "dashboard:visible-tiles-ready", -} as const; - -export function createDashboardPerformanceMarks( - options: DashboardPerformanceMarkOptions = {}, -) { - const mark = options.mark || - globalThis.performance?.mark?.bind(globalThis.performance); - let firstTileReadyMarked = false; - - function safeMark(name: string) { - try { - mark?.(name); - } catch { - // Performance marks are diagnostics only. - } - } - - return { - markAllTilesSettled(): void { - safeMark(dashboardPerformanceMarks.allTilesSettled); - }, - markFirstTileReady(): void { - if (firstTileReadyMarked) return; - firstTileReadyMarked = true; - safeMark(dashboardPerformanceMarks.firstTileReady); - }, - markShellLoad(): void { - firstTileReadyMarked = false; - safeMark(dashboardPerformanceMarks.shellLoad); - }, - markVisibleTilesReady(): void { - safeMark(dashboardPerformanceMarks.visibleTilesReady); - }, - }; -} - const dashboardTileBackoffDelaysMs = [15_000, 30_000, 60_000, 120_000]; export function createDashboardTileBackoff() { diff --git a/apps/web/src/server/routes/dashboard.test.ts b/apps/web/src/server/routes/dashboard.test.ts index d40e263..9b11079 100644 --- a/apps/web/src/server/routes/dashboard.test.ts +++ b/apps/web/src/server/routes/dashboard.test.ts @@ -1,4 +1,4 @@ -import { afterAll, afterEach, describe, expect, test, vi } from "vitest"; +import { describe, expect, test, vi } from "vitest"; import { dimensionLabDashboardFixture } from "$lib/dashboard-seed/dimensionlab"; import { createDashboardTileCache, @@ -6,20 +6,9 @@ import { handleDashboardTileRoute, loadDashboardResponse, loadDashboardTileResponse, - type DashboardTileResolutionLogEvent, } from "./dashboard"; describe("dashboard API route", () => { - const consoleInfo = vi.spyOn(console, "info").mockImplementation(() => undefined); - - afterEach(() => { - consoleInfo.mockClear(); - }); - - afterAll(() => { - consoleInfo.mockRestore(); - }); - test("returns the ready dashboard shell without hydrating live datasources", async () => { const fetch = vi.spyOn(globalThis, "fetch").mockRejectedValue( new Error("live datasource fetch should not run for the shell response"), @@ -165,13 +154,12 @@ describe("dashboard API route", () => { test("caches ready tile responses until the tile ttl expires", async () => { const cache = createDashboardTileCache(); let now = 1_000; - const fetch = vi.fn(async () => { - now += 7; - return jsonResponse({ + const fetch = vi.fn(async () => + jsonResponse({ status: "UP", ping: 42, - }); - }); + }), + ); const tile = { kind: "service", groupId: "essentials", id: "vaultwarden" } as const; const first = await loadDashboardTileResponse(tile, { @@ -202,68 +190,6 @@ describe("dashboard API route", () => { expect(fetch).toHaveBeenCalledTimes(2); }); - test("logs tile duration and cache hit or miss metadata", async () => { - let now = 1_000; - const logs: DashboardTileResolutionLogEvent[] = []; - const tile = { kind: "service", groupId: "essentials", id: "vaultwarden" } as const; - const service = dimensionLabDashboardFixture.serviceGroups - .flatMap((group) => group.services) - .find((item) => item.id === tile.id); - if (!service) throw new Error("missing service fixture"); - - let cacheCalls = 0; - const tileCache = { - async resolve() { - cacheCalls += 1; - if (cacheCalls === 1) now += 7; - const cacheState = cacheCalls === 1 ? "miss" as const : "hit" as const; - - return { - cache: cacheState, - coalesced: false, - response: { - state: "ready" as const, - tile, - item: service, - }, - }; - }, - }; - - await loadDashboardTileResponse(tile, { - logTileResolution: (event) => logs.push(event), - now: () => now, - refreshSeedDocument: true, - seedIfEmpty: true, - tileCache, - }); - now += 10; - await loadDashboardTileResponse(tile, { - logTileResolution: (event) => logs.push(event), - now: () => now, - refreshSeedDocument: true, - seedIfEmpty: true, - tileCache, - }); - - expect(logs).toEqual([ - expect.objectContaining({ - cache: "miss", - coalesced: false, - durationMs: 7, - status: "ready", - tileKey: dashboardTileCacheKey(tile), - }), - expect.objectContaining({ - cache: "hit", - coalesced: false, - durationMs: 0, - status: "ready", - tileKey: dashboardTileCacheKey(tile), - }), - ]); - }); - test("keeps telemetry tiles cached for fifteen seconds", async () => { const cache = createDashboardTileCache(); let now = 1_000; @@ -342,7 +268,6 @@ describe("dashboard API route", () => { test("coalesces concurrent tile requests for the same cache key", async () => { const cache = createDashboardTileCache(); - const logs: DashboardTileResolutionLogEvent[] = []; let resolveFetch: ((response: Response) => void) | undefined; const fetch = vi.fn(() => new Promise((resolve) => { @@ -353,7 +278,6 @@ describe("dashboard API route", () => { const first = loadDashboardTileResponse(tile, { fetch, - logTileResolution: (event) => logs.push(event), now: () => 1_000, refreshSeedDocument: true, seedIfEmpty: true, @@ -361,7 +285,6 @@ describe("dashboard API route", () => { }); const second = loadDashboardTileResponse(tile, { fetch, - logTileResolution: (event) => logs.push(event), now: () => 1_000, refreshSeedDocument: true, seedIfEmpty: true, @@ -373,104 +296,6 @@ describe("dashboard API route", () => { resolveFetch?.(jsonResponse({ status: "UP", ping: 42 })); expect(await first).toEqual(await second); - expect(logs).toEqual([ - expect.objectContaining({ - cache: "miss", - coalesced: false, - status: "ready", - tileKey: dashboardTileCacheKey(tile), - }), - expect.objectContaining({ - cache: "miss", - coalesced: true, - status: "ready", - tileKey: dashboardTileCacheKey(tile), - }), - ]); - }); - - test("logs coalesced metadata when shared tile requests fail", async () => { - const cache = createDashboardTileCache(); - let rejectLoad: ((error: Error) => void) | undefined; - const load = vi.fn(() => - new Promise((_resolve, reject) => { - rejectLoad = reject; - }) - ); - - const first = cache.resolve("tile-a", 30_000, 1_000, load); - const second = cache.resolve("tile-a", 30_000, 1_000, load); - - await Promise.resolve(); - expect(load).toHaveBeenCalledTimes(1); - - rejectLoad?.(new TypeError("upstream failed")); - await expect(first).rejects.toThrow("upstream failed"); - await expect(second).rejects.toMatchObject({ - cache: "miss", - coalesced: true, - cause: expect.any(TypeError), - }); - }); - - test("logs coalesced metadata for failed tile cache resolution", async () => { - const logs: DashboardTileResolutionLogEvent[] = []; - const tile = { kind: "service", groupId: "essentials", id: "vaultwarden" } as const; - - await expect( - loadDashboardTileResponse(tile, { - logTileResolution: (event) => logs.push(event), - refreshSeedDocument: true, - seedIfEmpty: true, - tileCache: { - async resolve() { - throw { - cache: "miss", - cause: new TypeError("coalesced cache failed"), - coalesced: true, - }; - }, - }, - }), - ).rejects.toThrow("coalesced cache failed"); - - expect(logs).toEqual([ - expect.objectContaining({ - cache: "miss", - coalesced: true, - errorCategory: "TypeError", - status: "error", - tileKey: dashboardTileCacheKey(tile), - }), - ]); - }); - - test("logs error categories for failed tile cache resolution", async () => { - const logs: DashboardTileResolutionLogEvent[] = []; - const tile = { kind: "service", groupId: "essentials", id: "vaultwarden" } as const; - - await expect( - loadDashboardTileResponse(tile, { - logTileResolution: (event) => logs.push(event), - refreshSeedDocument: true, - seedIfEmpty: true, - tileCache: { - async resolve() { - throw new TypeError("cache failed"); - }, - }, - }), - ).rejects.toThrow("cache failed"); - - expect(logs).toEqual([ - expect.objectContaining({ - cache: "miss", - coalesced: false, - errorCategory: "TypeError", - status: "error", - tileKey: dashboardTileCacheKey(tile), - }), - ]); }); test("uses structured tile cache keys when identifiers contain delimiters", () => { diff --git a/apps/web/src/server/routes/dashboard.ts b/apps/web/src/server/routes/dashboard.ts index 42c4088..ce9c4e6 100644 --- a/apps/web/src/server/routes/dashboard.ts +++ b/apps/web/src/server/routes/dashboard.ts @@ -20,7 +20,6 @@ export interface LoadDashboardResponseOptions DatasourceResolutionOptions { disableLiveDatasources?: boolean; hydrateLiveDatasources?: boolean; - logTileResolution?: (event: DashboardTileResolutionLogEvent) => void; now?: () => number; tileCache?: DashboardTileCache; } @@ -36,7 +35,7 @@ export interface DashboardTileCache { ttlMs: number, now: number, load: () => Promise, - ): Promise; + ): Promise; } const dashboardTileResponseHeaders = { @@ -45,21 +44,6 @@ const dashboardTileResponseHeaders = { const defaultDashboardTileCache = createDashboardTileCache(); -interface DashboardTileCacheResult { - cache: "hit" | "miss"; - coalesced: boolean; - response: DashboardTileResolution; -} - -export interface DashboardTileResolutionLogEvent { - cache: "bypass" | "hit" | "miss"; - coalesced: boolean; - durationMs: number; - errorCategory?: string; - status: DashboardTileResolution["state"] | "error"; - tileKey: string; -} - export function createDashboardTileCache(): DashboardTileCache { const entries = new Map(); const inFlight = new Map>(); @@ -68,28 +52,11 @@ export function createDashboardTileCache(): DashboardTileCache { async resolve(key, ttlMs, now, load) { const cached = entries.get(key); if (cached && cached.expiresAt > now) { - return { - cache: "hit", - coalesced: false, - response: cached.response, - }; + return cached.response; } const active = inFlight.get(key); - if (active) { - return active - .then((response) => ({ - cache: "miss" as const, - coalesced: true, - response, - })) - .catch((error) => { - throw new DashboardTileCacheResolutionError(error, { - cache: "miss", - coalesced: true, - }); - }); - } + if (active) return active; const request = load() .then((response) => { @@ -107,11 +74,7 @@ export function createDashboardTileCache(): DashboardTileCache { }); inFlight.set(key, request); - return request.then((response) => ({ - cache: "miss" as const, - coalesced: false, - response, - })); + return request; }, }; } @@ -162,26 +125,13 @@ export async function loadDashboardTileResponse( options.disableLiveDatasources || process.env.DISABLE_LIVE_DATASOURCES === "1" ) { - const tileKey = dashboardTileCacheKey(tile); - const startedAt = options.now?.() ?? Date.now(); - const response = { + return { state: "disabled", tile, message: "Live datasource hydration is disabled.", - } satisfies DashboardTileResolution; - logDashboardTileResolution(options, { - cache: "bypass", - coalesced: false, - durationMs: elapsedDashboardTileMs(startedAt, options), - status: response.state, - tileKey, - }); - return response; + }; } - const tileKey = dashboardTileCacheKey(tile); - const startedAt = options.now?.() ?? Date.now(); - const dashboard = loadDashboardRuntime(undefined, { refreshSeedDocument: options.refreshSeedDocument ?? true, seedIfEmpty: options.seedIfEmpty ?? true, @@ -189,51 +139,22 @@ export async function loadDashboardTileResponse( }); if (dashboard.state !== "ready") { - const response = { + return { state: "not_found", tile, message: `Dashboard is not ready: ${dashboard.state}`, - } satisfies DashboardTileResolution; - logDashboardTileResolution(options, { - cache: "bypass", - coalesced: false, - durationMs: elapsedDashboardTileMs(startedAt, options), - status: response.state, - tileKey, - }); - return response; + }; } const cache = options.tileCache || defaultDashboardTileCache; const now = options.now?.() ?? Date.now(); - try { - const result = await cache.resolve( - tileKey, - dashboardTileTtlMs(dashboard.document, tile), - now, - () => resolveDashboardTile(dashboard.document, tile, options), - ); - logDashboardTileResolution(options, { - cache: result.cache, - coalesced: result.coalesced, - durationMs: elapsedDashboardTileMs(startedAt, options), - status: result.response.state, - tileKey, - }); - return result.response; - } catch (error) { - const cacheError = dashboardTileCacheResolutionError(error); - logDashboardTileResolution(options, { - cache: cacheError?.cache ?? "miss", - coalesced: cacheError?.coalesced ?? false, - durationMs: elapsedDashboardTileMs(startedAt, options), - errorCategory: dashboardTileErrorCategory(cacheError?.cause ?? error), - status: "error", - tileKey, - }); - throw cacheError?.cause ?? error; - } + return cache.resolve( + dashboardTileCacheKey(tile), + dashboardTileTtlMs(dashboard.document, tile), + now, + () => resolveDashboardTile(dashboard.document, tile, options), + ); } export async function handleDashboardRoute(): Promise { @@ -263,84 +184,6 @@ export function dashboardTileCacheKey(tile: DashboardTileReference): string { return JSON.stringify(tile); } -function logDashboardTileResolution( - options: LoadDashboardResponseOptions, - event: DashboardTileResolutionLogEvent, -): void { - if (options.logTileResolution) { - options.logTileResolution(event); - return; - } - - console.info("dashboard.tile", event); -} - -function elapsedDashboardTileMs( - startedAt: number, - options: LoadDashboardResponseOptions, -): number { - const now = options.now?.() ?? Date.now(); - return Math.max(0, now - startedAt); -} - -function dashboardTileErrorCategory(error: unknown): string { - if ( - typeof error === "object" && - error !== null && - "name" in error && - typeof error.name === "string" - ) { - return error.name; - } - - return "unknown"; -} - -class DashboardTileCacheResolutionError extends Error { - readonly cache: "hit" | "miss"; - readonly coalesced: boolean; - override readonly cause: unknown; - - constructor( - cause: unknown, - metadata: Pick, - ) { - super("Dashboard tile cache resolution failed"); - this.name = "DashboardTileCacheResolutionError"; - this.cause = cause; - this.cache = metadata.cache; - this.coalesced = metadata.coalesced; - } -} - -function dashboardTileCacheResolutionError( - error: unknown, -): DashboardTileCacheResolutionFailure | null { - if ( - typeof error === "object" && - error !== null && - "cache" in error && - (error.cache === "hit" || error.cache === "miss") && - "coalesced" in error && - typeof error.coalesced === "boolean" && - "cause" in error - ) { - return { - cache: error.cache, - cause: error.cause, - coalesced: error.coalesced, - }; - } - - return null; -} - -interface DashboardTileCacheResolutionFailure { - cache: "hit" | "miss"; - cause: unknown; - coalesced: boolean; -} - function dashboardTileTtlMs( document: DashboardDocument, tile: DashboardTileReference,