From fede33e8a7deb12c5b38e88a4decde4382edafa2 Mon Sep 17 00:00:00 2001 From: vince Date: Thu, 18 Jun 2026 19:22:55 +0200 Subject: [PATCH 1/3] feat: render dashboard from model --- src/lib/server/dashboard.test.ts | 24 +++- src/lib/server/dashboard.ts | 75 +++++++--- src/lib/ui/components/FooterCell.svelte | 3 +- src/lib/ui/components/ModuleCard.svelte | 2 +- src/lib/ui/components/ServiceRow.svelte | 3 +- src/lib/ui/components/TelemetryCard.svelte | 2 +- src/lib/ui/index.ts | 1 + src/lib/ui/model-renderer.test.ts | 90 ++++++++++++ src/lib/ui/model-renderer.ts | 109 ++++++++++++++ src/routes/+page.svelte | 160 ++++++++------------- src/routes/page.test.ts | 42 ++++-- 11 files changed, 372 insertions(+), 139 deletions(-) create mode 100644 src/lib/ui/model-renderer.test.ts create mode 100644 src/lib/ui/model-renderer.ts diff --git a/src/lib/server/dashboard.test.ts b/src/lib/server/dashboard.test.ts index 91a5ffb..5f1fc98 100644 --- a/src/lib/server/dashboard.test.ts +++ b/src/lib/server/dashboard.test.ts @@ -4,6 +4,7 @@ import { tmpdir } from "node:os"; import { join } from "node:path"; import { afterEach, describe, expect, test } from "vitest"; import { dimensionLabDashboardFixture } from "$lib/model/fixtures/dimensionlab"; +import { genericDashboardFixture } from "$lib/model/fixtures/generic"; import { createDashboardStore, type DashboardStore } from "./db/dashboard-store"; import { loadDashboardRuntime } from "./dashboard"; @@ -21,13 +22,30 @@ describe("dashboard runtime loader", () => { const runtime = loadDashboardRuntime(store); - expect(runtime.title).toBe(dimensionLabDashboardFixture.metadata.title); - expect(runtime.subtitle).toBe(dimensionLabDashboardFixture.metadata.subtitle); - expect(runtime.status).toBe("building"); + expect(runtime.state).toBe("ready"); + if (runtime.state !== "ready") throw new Error("expected ready dashboard"); + expect(runtime.document.metadata.title).toBe(dimensionLabDashboardFixture.metadata.title); + expect(runtime.document.metadata.subtitle).toBe(dimensionLabDashboardFixture.metadata.subtitle); expect(runtime.schemaVersion).toBe(dimensionLabDashboardFixture.schemaVersion); expect(runtime.currentRevisionId).toBe(store.getActiveDashboard()?.currentRevisionId); expect(store.listRevisions()).toHaveLength(1); }); + + test("loads an existing active dashboard without reseeding", async () => { + const store = await createTestStore(); + const seed = store.commitDashboard(genericDashboardFixture, { + actor: "test", + message: "existing dashboard", + }); + + const runtime = loadDashboardRuntime(store); + + expect(runtime.state).toBe("ready"); + if (runtime.state !== "ready") throw new Error("expected ready dashboard"); + expect(runtime.currentRevisionId).toBe(seed.id); + expect(runtime.document.metadata.title).toBe(genericDashboardFixture.metadata.title); + expect(store.listRevisions()).toHaveLength(1); + }); }); async function createTestStore() { diff --git a/src/lib/server/dashboard.ts b/src/lib/server/dashboard.ts index 56380a4..c470399 100644 --- a/src/lib/server/dashboard.ts +++ b/src/lib/server/dashboard.ts @@ -1,31 +1,42 @@ import { dimensionLabDashboardFixture } from "$lib/model/fixtures/dimensionlab"; +import type { DashboardDocument } from "$lib/model"; import { createDashboardStore, + DashboardPersistenceValidationError, type DashboardStore, } from "$lib/server/db/dashboard-store"; +import { UnsupportedDashboardModelVersionError } from "$lib/server/db/model-migrations"; -export type PlaceholderStatus = "ready" | "building"; +export type DashboardRuntimeState = + | DashboardRuntimeEmpty + | DashboardRuntimeInvalid + | DashboardRuntimeReady; -export interface PlaceholderDashboard { +export interface DashboardRuntimeReady { + state: "ready"; + document: DashboardDocument; + schemaVersion: string; + currentRevisionId: string; +} + +export interface DashboardRuntimeEmpty { + state: "empty"; title: string; subtitle: string; - status: PlaceholderStatus; message: string; - schemaVersion?: string; - currentRevisionId?: string; } -export function loadPlaceholderDashboard(): PlaceholderDashboard { - return { - title: "Dashboard Runtime", - subtitle: "Runtime scaffold", - status: "building", - message: - "SvelteKit is running. Later issues will replace this placeholder with the validated dashboard model.", - }; +export interface DashboardRuntimeInvalid { + state: "invalid"; + title: string; + subtitle: string; + message: string; + errors: string[]; } -export function loadDashboardRuntime(store?: DashboardStore): PlaceholderDashboard { +export function loadDashboardRuntime( + store?: DashboardStore, +): DashboardRuntimeState { const dashboardStore = store || createDashboardStore(); try { @@ -37,16 +48,42 @@ export function loadDashboardRuntime(store?: DashboardStore): PlaceholderDashboa const document = active?.document || seeded.document; const currentRevisionId = active?.currentRevisionId || seeded.id; + if (!document) { + return { + state: "empty", + title: "No Dashboard Model", + subtitle: "No active document", + message: "No validated dashboard document is active yet.", + }; + } + return { - title: document.metadata.title, - subtitle: document.metadata.subtitle || "", - status: "building", - message: - "Active dashboard document loaded from SQLite. Later issues will replace this placeholder with model-driven rendering.", + state: "ready", + document, schemaVersion: document.schemaVersion, currentRevisionId, }; + } catch (error) { + if (error instanceof DashboardPersistenceValidationError) { + return invalidRuntimeState(error.failure.errors); + } + + if (error instanceof UnsupportedDashboardModelVersionError) { + return invalidRuntimeState([error.message]); + } + + throw error; } finally { if (!store) dashboardStore.close(); } } + +function invalidRuntimeState(errors: string[]): DashboardRuntimeInvalid { + return { + state: "invalid", + title: "Invalid Dashboard Model", + subtitle: "Validation failed", + message: "The active dashboard document could not be validated.", + errors, + }; +} diff --git a/src/lib/ui/components/FooterCell.svelte b/src/lib/ui/components/FooterCell.svelte index d4261fe..b71cc66 100644 --- a/src/lib/ui/components/FooterCell.svelte +++ b/src/lib/ui/components/FooterCell.svelte @@ -16,6 +16,7 @@ {:else} -