diff --git a/bun.lock b/bun.lock
index 6bf41c5..335ff14 100644
--- a/bun.lock
+++ b/bun.lock
@@ -5,6 +5,7 @@
"": {
"name": "dimensionlab-website",
"dependencies": {
+ "@iconify/svelte": "^5.2.2",
"@sinclair/typebox": "^0.34.49",
"ajv": "^8.20.0",
"ajv-formats": "^3.0.1",
@@ -90,6 +91,10 @@
"@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.12", "", { "os": "win32", "cpu": "x64" }, "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA=="],
+ "@iconify/svelte": ["@iconify/svelte@5.2.2", "", { "dependencies": { "@iconify/types": "^2.0.0" }, "peerDependencies": { "svelte": ">5.0.0" } }, "sha512-XMXxD3nzH7yB68C3K4sNwzrJ1TBscODR0ZqCeNf0KGuEMCTSuCo0jHNDZ0o0iRXTylO41NSz/DWam/4atLG1yw=="],
+
+ "@iconify/types": ["@iconify/types@2.0.0", "", {}, "sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg=="],
+
"@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.13", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="],
"@jridgewell/remapping": ["@jridgewell/remapping@2.3.5", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ=="],
diff --git a/package.json b/package.json
index fcf2270..2bef9c3 100644
--- a/package.json
+++ b/package.json
@@ -13,6 +13,7 @@
"db:check": "drizzle-kit check"
},
"dependencies": {
+ "@iconify/svelte": "^5.2.2",
"@sinclair/typebox": "^0.34.49",
"ajv": "^8.20.0",
"ajv-formats": "^3.0.1",
diff --git a/src/app.css b/src/app.css
index ea0c565..4fa34dd 100644
--- a/src/app.css
+++ b/src/app.css
@@ -1,30 +1 @@
-:root {
- color-scheme: dark;
- font-family:
- "IBM Plex Mono", "Roboto Mono", "SFMono-Regular", Consolas, monospace;
- background: #050505;
- color: #f4f4f4;
- text-rendering: geometricPrecision;
-}
-
-body {
- min-width: 320px;
- min-height: 100vh;
- margin: 0;
- background:
- linear-gradient(rgba(255, 255, 255, 0.035) 1px, transparent 1px),
- linear-gradient(90deg, rgba(255, 255, 255, 0.035) 1px, transparent 1px),
- #050505;
- background-size: 48px 48px;
-}
-
-button,
-input,
-textarea,
-select {
- font: inherit;
-}
-
-a {
- color: inherit;
-}
+@import "./lib/ui/tokens.css";
diff --git a/src/lib/ui/components/DashboardFrame.svelte b/src/lib/ui/components/DashboardFrame.svelte
new file mode 100644
index 0000000..0a76f67
--- /dev/null
+++ b/src/lib/ui/components/DashboardFrame.svelte
@@ -0,0 +1,116 @@
+
+
+
+
+
+
+
+
+ {#each dashboard.serviceGroups as group (group.id)}
+
+ {/each}
+
+
+
+
+
+
diff --git a/src/lib/ui/components/FooterCell.svelte b/src/lib/ui/components/FooterCell.svelte
new file mode 100644
index 0000000..d4261fe
--- /dev/null
+++ b/src/lib/ui/components/FooterCell.svelte
@@ -0,0 +1,89 @@
+
+
+{#snippet cellContent()}
+ {item.label}
+ {item.value}
+{/snippet}
+
+{#if item.link}
+
+{:else}
+
+{/if}
+
+
diff --git a/src/lib/ui/components/IconGlyph.svelte b/src/lib/ui/components/IconGlyph.svelte
new file mode 100644
index 0000000..81318d6
--- /dev/null
+++ b/src/lib/ui/components/IconGlyph.svelte
@@ -0,0 +1,53 @@
+
+
+
+ {#if name}
+
+ {/if}
+
+
+
diff --git a/src/lib/ui/components/ModuleCard.svelte b/src/lib/ui/components/ModuleCard.svelte
new file mode 100644
index 0000000..21a2854
--- /dev/null
+++ b/src/lib/ui/components/ModuleCard.svelte
@@ -0,0 +1,86 @@
+
+
+
+
+
diff --git a/src/lib/ui/components/Panel.svelte b/src/lib/ui/components/Panel.svelte
new file mode 100644
index 0000000..cbc68e3
--- /dev/null
+++ b/src/lib/ui/components/Panel.svelte
@@ -0,0 +1,81 @@
+
+
+
+ {#if title}
+
+ {/if}
+
+ {@render children?.()}
+
+
+
+
diff --git a/src/lib/ui/components/ServicePanel.svelte b/src/lib/ui/components/ServicePanel.svelte
new file mode 100644
index 0000000..c292535
--- /dev/null
+++ b/src/lib/ui/components/ServicePanel.svelte
@@ -0,0 +1,17 @@
+
+
+
+ {#if group.summary?.length}
+
+ {/if}
+ {#each group.services as service (service.id)}
+
+ {/each}
+
diff --git a/src/lib/ui/components/ServiceRow.svelte b/src/lib/ui/components/ServiceRow.svelte
new file mode 100644
index 0000000..0021424
--- /dev/null
+++ b/src/lib/ui/components/ServiceRow.svelte
@@ -0,0 +1,106 @@
+
+
+{#snippet rowContent()}
+
+
+
{service.label}
+
{service.description}
+
+ {#if service.detail}
+
+ {/if}
+{/snippet}
+
+{#if service.link}
+
+ {@render rowContent()}
+
+{:else}
+
+ {@render rowContent()}
+
+{/if}
+
+
diff --git a/src/lib/ui/components/StatusBadge.svelte b/src/lib/ui/components/StatusBadge.svelte
new file mode 100644
index 0000000..a0fa058
--- /dev/null
+++ b/src/lib/ui/components/StatusBadge.svelte
@@ -0,0 +1,52 @@
+
+
+{label}
+
+
diff --git a/src/lib/ui/components/StatusStrip.svelte b/src/lib/ui/components/StatusStrip.svelte
new file mode 100644
index 0000000..6a2bcf1
--- /dev/null
+++ b/src/lib/ui/components/StatusStrip.svelte
@@ -0,0 +1,32 @@
+
+
+
+ {#each items as item (item.id)}
+
+ {/each}
+
+
+
diff --git a/src/lib/ui/components/TelemetryCard.svelte b/src/lib/ui/components/TelemetryCard.svelte
new file mode 100644
index 0000000..960aeaa
--- /dev/null
+++ b/src/lib/ui/components/TelemetryCard.svelte
@@ -0,0 +1,158 @@
+
+
+
+
+ {#if card.icon}
+
+ {/if}
+ {card.label}
+
+ {value}
+ {#if progress !== null}
+
+
+
+ {/if}
+ {#if card.sparkline?.length}
+
+ {/if}
+ {#if card.detail || card.description}
+ {card.detail || card.description}
+ {/if}
+
+
+
+
+
diff --git a/src/lib/ui/components/TelemetryGrid.svelte b/src/lib/ui/components/TelemetryGrid.svelte
new file mode 100644
index 0000000..aead537
--- /dev/null
+++ b/src/lib/ui/components/TelemetryGrid.svelte
@@ -0,0 +1,22 @@
+
+
+
+ {#each cards as card (card.id)}
+
+ {/each}
+
+
+
diff --git a/src/lib/ui/components/render.test.ts b/src/lib/ui/components/render.test.ts
new file mode 100644
index 0000000..dd26b81
--- /dev/null
+++ b/src/lib/ui/components/render.test.ts
@@ -0,0 +1,97 @@
+import { render } from "svelte/server";
+import { describe, expect, test } from "vitest";
+import DashboardFrame from "./DashboardFrame.svelte";
+import FooterCell from "./FooterCell.svelte";
+import ServiceRow from "./ServiceRow.svelte";
+import TelemetryCard from "./TelemetryCard.svelte";
+import { dashboardPreviewFixtures } from "../fixtures";
+
+describe("dashboard UI components", () => {
+ test("renders the primary generic dashboard fixture", () => {
+ const { body } = render(DashboardFrame, {
+ props: {
+ dashboard: dashboardPreviewFixtures.primary,
+ },
+ });
+
+ expect(body).toContain("Operations Console");
+ expect(body).toContain("Core Throughput");
+ expect(body).toContain("Queue Workers");
+ expect(body).toContain("data-icon-name=\"mdi:server-network\"");
+ expect(body).toContain("data-severity=\"warning\"");
+ expect(body).not.toContain("dashboard-title");
+ });
+
+ test("renders another generic dashboard fixture through the same component", () => {
+ const { body } = render(DashboardFrame, {
+ props: {
+ dashboard: dashboardPreviewFixtures.secondary,
+ },
+ });
+
+ expect(body).toContain("Support Desk");
+ expect(body).toContain("Response Window");
+ expect(body).toContain("Regional Nodes");
+ expect(body).toContain("data-icon-name=\"mdi:headset\"");
+ expect(body).toContain("data-severity=\"loading\"");
+ });
+
+ test("renders optional service and status links as focusable anchors", () => {
+ const service = render(ServiceRow, {
+ props: {
+ service: {
+ id: "linked-service",
+ label: "Linked Service",
+ description: "Generic linked destination",
+ severity: "ok",
+ detail: "ready",
+ link: { href: "https://example.test/service", external: true },
+ },
+ },
+ });
+ const footer = render(FooterCell, {
+ props: {
+ item: {
+ id: "linked-status",
+ label: "Linked Status",
+ value: "open",
+ severity: "neutral",
+ link: { href: "https://example.test/status" },
+ },
+ },
+ });
+
+ expect(service.body).toContain(" {
+ const withoutProgress = render(TelemetryCard, {
+ props: {
+ card: {
+ id: "bytes",
+ label: "Bytes Metric",
+ value: { kind: "bytes", value: 2048 },
+ severity: "neutral",
+ },
+ },
+ });
+ const withProgress = render(TelemetryCard, {
+ props: {
+ card: {
+ id: "bytes-with-progress",
+ label: "Bytes With Progress",
+ value: { kind: "bytes", value: 2048 },
+ progress: 42,
+ severity: "neutral",
+ },
+ },
+ });
+
+ expect(withoutProgress.body).not.toContain("telemetry-card__bar");
+ expect(withProgress.body).toContain("--metric-progress: 42%");
+ });
+});
diff --git a/src/lib/ui/content-boundary.test.ts b/src/lib/ui/content-boundary.test.ts
new file mode 100644
index 0000000..6613644
--- /dev/null
+++ b/src/lib/ui/content-boundary.test.ts
@@ -0,0 +1,49 @@
+import { readdirSync, readFileSync, statSync } from "node:fs";
+import { join } from "node:path";
+import { describe, expect, test } from "vitest";
+
+const forbiddenTerms = [
+ "dimensionlab",
+ "dimension lab",
+ "vaultwarden",
+ "forgejo",
+ "grafana",
+ "uptime kuma",
+ "prometheus",
+ "backrest",
+ "open webui",
+ "comfyui",
+ "adminer",
+ "cockpit",
+ "ollama",
+];
+
+describe("UI content boundary", () => {
+ test("keeps environment-specific content out of reusable UI source", () => {
+ const source = readUiSource(join(process.cwd(), "src", "lib", "ui"));
+ const normalized = source.toLowerCase();
+
+ expect(
+ forbiddenTerms.filter((term) => normalized.includes(term)),
+ ).toEqual([]);
+ });
+
+ test("keeps icon rendering driven by icon identifiers", () => {
+ const source = readUiSource(join(process.cwd(), "src", "lib", "ui"));
+
+ expect(source).not.toContain("@iconify-json/");
+ expect(source).not.toContain("/icons/");
+ });
+});
+
+function readUiSource(path: string): string {
+ const stats = statSync(path);
+ if (stats.isFile()) {
+ if (path.endsWith(".test.ts")) return "";
+ return readFileSync(path, "utf8");
+ }
+
+ return readdirSync(path)
+ .map((entry) => readUiSource(join(path, entry)))
+ .join("\n");
+}
diff --git a/src/lib/ui/fixtures.ts b/src/lib/ui/fixtures.ts
new file mode 100644
index 0000000..54f8b48
--- /dev/null
+++ b/src/lib/ui/fixtures.ts
@@ -0,0 +1,130 @@
+import type { UiDashboardPreview } from "./types";
+
+export const dashboardPreviewFixtures: Record = {
+ primary: {
+ eyebrow: "Command Surface",
+ title: "Operations Console",
+ subtitle: "Capacity, latency, and queue health",
+ telemetry: [
+ metric("core-throughput", "Core Throughput", "mdi:server-network", 82, "ok"),
+ metric("edge-cache", "Edge Cache", "mdi:database-clock", 67, "neutral"),
+ metric("queue-depth", "Queue Depth", "mdi:tray-full", 74, "warning"),
+ metric("error-budget", "Error Budget", "mdi:chart-timeline-variant", 18, "danger"),
+ metric("build-latency", "Build Latency", "mdi:timer-sand", 23, "stale"),
+ metric("worker-load", "Worker Load", "mdi:cpu-64-bit", 55, "ok"),
+ ],
+ modules: [
+ {
+ id: "ambient-conditions",
+ title: "Local Node",
+ value: "21.4 C",
+ detail: "clear window",
+ icon: "mdi:weather-partly-cloudy",
+ severity: "ok",
+ },
+ ],
+ serviceGroups: [
+ {
+ id: "queue-workers",
+ title: "Queue Workers",
+ services: [
+ service("ingest", "Ingest", "Event intake pipeline", "mdi:arrow-collapse-down", "ok", "1.252 ms"),
+ service("scheduler", "Scheduler", "Timed job coordinator", "mdi:calendar-clock", "warning", "1.487 ms"),
+ service("archive", "Archive", "Cold storage transfer", "mdi:archive-arrow-down", "ok", "1.301 ms"),
+ ],
+ },
+ {
+ id: "regional-nodes",
+ title: "Regional Nodes",
+ services: [
+ service("north", "North", "Primary traffic cell", "mdi:access-point", "ok", "899 ms"),
+ service("west", "West", "Replica traffic cell", "mdi:access-point-network", "neutral", "1.118 ms"),
+ service("south", "South", "Maintenance window", "mdi:wrench-clock", "stale", "paused"),
+ ],
+ },
+ {
+ id: "control-plane",
+ title: "Control Plane",
+ services: [
+ service("identity", "Identity", "Access token exchange", "mdi:account-key", "ok", "721 ms"),
+ service("policy", "Policy", "Rules evaluation", "mdi:shield-check", "ok", "812 ms"),
+ service("audit", "Audit", "Event ledger writer", "mdi:text-box-search", "warning", "1.442 ms"),
+ ],
+ },
+ ],
+ statusItems: [
+ { id: "system", label: "System", value: "Nominal", severity: "ok" },
+ { id: "sync", label: "Last Sync", value: "2 minutes ago", severity: "stale" },
+ { id: "uptime", label: "Uptime", value: "14d 08h", severity: "neutral" },
+ { id: "refresh", label: "Refresh", value: "15s", severity: "neutral" },
+ ],
+ },
+ secondary: {
+ eyebrow: "Support Surface",
+ title: "Support Desk",
+ subtitle: "Response, routing, and regional health",
+ telemetry: [
+ metric("response-window", "Response Window", "mdi:headset", 41, "ok"),
+ metric("ticket-load", "Ticket Load", "mdi:ticket-confirmation", 68, "warning"),
+ metric("handoff-drift", "Handoff Drift", "mdi:swap-horizontal", 11, "neutral"),
+ metric("coverage-gap", "Coverage Gap", "mdi:map-marker-alert", 7, "danger"),
+ metric("routing-warmup", "Routing Warmup", "mdi:progress-clock", 0, "loading"),
+ ],
+ modules: [
+ {
+ id: "shift-state",
+ title: "Shift State",
+ value: "covered",
+ detail: "static fixture",
+ icon: "mdi:clipboard-check-outline",
+ severity: "ok",
+ },
+ ],
+ serviceGroups: [
+ {
+ id: "regional-nodes",
+ title: "Regional Nodes",
+ services: [
+ service("desk-a", "Desk A", "Frontline queue", "mdi:monitor-dashboard", "ok", "843 ms"),
+ service("desk-b", "Desk B", "Escalation queue", "mdi:monitor-star", "warning", "1.204 ms"),
+ service("desk-c", "Desk C", "Overflow queue", "mdi:monitor-off", "unavailable", "offline"),
+ ],
+ },
+ ],
+ statusItems: [
+ { id: "desk", label: "Desk", value: "Covered", severity: "ok" },
+ { id: "handoff", label: "Handoff", value: "Pending", severity: "warning" },
+ { id: "routing", label: "Routing", value: "Manual", severity: "neutral" },
+ ],
+ },
+};
+
+function metric(
+ id: string,
+ label: string,
+ icon: string,
+ progress: number,
+ severity: UiDashboardPreview["telemetry"][number]["severity"],
+) {
+ return {
+ id,
+ label,
+ icon,
+ progress,
+ severity,
+ value: { kind: "percent" as const, value: progress },
+ detail: "static fixture",
+ sparkline: [12, 18, 15, 28, 24, progress],
+ };
+}
+
+function service(
+ id: string,
+ label: string,
+ description: string,
+ icon: string,
+ severity: UiDashboardPreview["serviceGroups"][number]["services"][number]["severity"],
+ detail: string,
+) {
+ return { id, label, description, icon, severity, detail };
+}
diff --git a/src/lib/ui/format.ts b/src/lib/ui/format.ts
new file mode 100644
index 0000000..4a8a5f1
--- /dev/null
+++ b/src/lib/ui/format.ts
@@ -0,0 +1,41 @@
+import type { UiMetricValue } from "./types";
+
+const byteUnits = ["B", "KB", "MB", "GB", "TB"];
+
+export function formatMetricValue(metric: UiMetricValue): string {
+ if (metric.kind === "text") return String(metric.value);
+
+ const value = typeof metric.value === "number" ? metric.value : Number(metric.value);
+ const precision = metric.precision ?? inferPrecision(value);
+ const unit = metric.unit ?? defaultUnit(metric.kind);
+
+ if (metric.kind === "bytes") return formatBytes(value, precision);
+ return `${value.toFixed(precision)}${unit ? ` ${unit}` : ""}`;
+}
+
+export function clampPercent(value = 0): number {
+ if (!Number.isFinite(value)) return 0;
+ return Math.max(0, Math.min(100, value));
+}
+
+function defaultUnit(kind: UiMetricValue["kind"]): string {
+ if (kind === "percent") return "%";
+ if (kind === "temperature") return "C";
+ if (kind === "latency") return "ms";
+ return "";
+}
+
+function inferPrecision(value: number): number {
+ return Number.isInteger(value) ? 0 : 1;
+}
+
+function formatBytes(value: number, precision: number): string {
+ let size = value;
+ let index = 0;
+ while (size >= 1024 && index < byteUnits.length - 1) {
+ size /= 1024;
+ index += 1;
+ }
+
+ return `${size.toFixed(precision)} ${byteUnits[index]}`;
+}
diff --git a/src/lib/ui/index.ts b/src/lib/ui/index.ts
new file mode 100644
index 0000000..df2a02c
--- /dev/null
+++ b/src/lib/ui/index.ts
@@ -0,0 +1,23 @@
+export { default as DashboardFrame } from "./components/DashboardFrame.svelte";
+export { default as FooterCell } from "./components/FooterCell.svelte";
+export { default as IconGlyph } from "./components/IconGlyph.svelte";
+export { default as ModuleCard } from "./components/ModuleCard.svelte";
+export { default as Panel } from "./components/Panel.svelte";
+export { default as ServicePanel } from "./components/ServicePanel.svelte";
+export { default as ServiceRow } from "./components/ServiceRow.svelte";
+export { default as StatusBadge } from "./components/StatusBadge.svelte";
+export { default as StatusStrip } from "./components/StatusStrip.svelte";
+export { default as TelemetryCard } from "./components/TelemetryCard.svelte";
+export { default as TelemetryGrid } from "./components/TelemetryGrid.svelte";
+export { dashboardPreviewFixtures } from "./fixtures";
+export type {
+ UiDashboardPreview,
+ UiLink,
+ UiMetricValue,
+ UiModuleBlock,
+ UiServiceGroup,
+ UiServiceRow,
+ UiSeverity,
+ UiStatusItem,
+ UiTelemetryCard,
+} from "./types";
diff --git a/src/lib/ui/tokens.css b/src/lib/ui/tokens.css
new file mode 100644
index 0000000..56c2f7e
--- /dev/null
+++ b/src/lib/ui/tokens.css
@@ -0,0 +1,81 @@
+:root {
+ color-scheme: dark;
+ --ui-color-canvas: #020302;
+ --ui-color-surface: #060706;
+ --ui-color-surface-raised: #0b0d0c;
+ --ui-color-line: rgba(244, 244, 244, 0.16);
+ --ui-color-line-strong: rgba(244, 244, 244, 0.32);
+ --ui-color-text: #f3f4ed;
+ --ui-color-muted: #8d948c;
+ --ui-color-dim: #555b55;
+ --ui-color-accent: #d7ff00;
+ --ui-color-ok: #bfff00;
+ --ui-color-warning: #ffb020;
+ --ui-color-danger: #ff1744;
+ --ui-color-stale: #82909a;
+ --ui-color-unavailable: #65707a;
+ --ui-font-mono: "IBM Plex Mono", "Roboto Mono", "SFMono-Regular", Consolas, monospace;
+ --ui-font-display: "Teko", "IBM Plex Mono", "Roboto Mono", monospace;
+ --ui-space-1: 0.25rem;
+ --ui-space-2: 0.5rem;
+ --ui-space-3: 0.75rem;
+ --ui-space-4: 1rem;
+ --ui-space-5: 1.25rem;
+ --ui-space-6: 1.5rem;
+ --ui-radius-none: 0;
+ --ui-border: 1px solid var(--ui-color-line);
+ --ui-border-strong: 1px solid var(--ui-color-line-strong);
+ --ui-shadow-hard: 0 0 0 1px rgba(215, 255, 0, 0.12) inset;
+ --ui-z-base: 0;
+ --ui-z-panel: 1;
+ --ui-z-overlay: 10;
+ --ui-density-card-min: 9.75rem;
+ --ui-focus-ring: 0 0 0 2px var(--ui-color-canvas), 0 0 0 4px var(--ui-color-accent);
+}
+
+html {
+ background: var(--ui-color-canvas);
+}
+
+body {
+ min-width: 320px;
+ min-height: 100vh;
+ margin: 0;
+ background:
+ linear-gradient(rgba(255, 255, 255, 0.035) 1px, transparent 1px),
+ linear-gradient(90deg, rgba(255, 255, 255, 0.035) 1px, transparent 1px),
+ var(--ui-color-canvas);
+ background-size: 48px 48px, 48px 48px, auto;
+ color: var(--ui-color-text);
+ font-family: var(--ui-font-mono);
+ text-rendering: geometricPrecision;
+}
+
+button,
+input,
+textarea,
+select {
+ font: inherit;
+}
+
+button:focus-visible,
+a:focus-visible,
+[tabindex]:focus-visible {
+ outline: 0;
+ box-shadow: var(--ui-focus-ring);
+}
+
+a {
+ color: inherit;
+}
+
+@media (prefers-reduced-motion: reduce) {
+ *,
+ *::before,
+ *::after {
+ animation-duration: 0.01ms !important;
+ animation-iteration-count: 1 !important;
+ scroll-behavior: auto !important;
+ transition-duration: 0.01ms !important;
+ }
+}
diff --git a/src/lib/ui/types.ts b/src/lib/ui/types.ts
new file mode 100644
index 0000000..1844491
--- /dev/null
+++ b/src/lib/ui/types.ts
@@ -0,0 +1,86 @@
+export type UiSeverity =
+ | "neutral"
+ | "ok"
+ | "warning"
+ | "danger"
+ | "stale"
+ | "unavailable"
+ | "loading";
+
+export type UiMetricKind =
+ | "bytes"
+ | "latency"
+ | "number"
+ | "percent"
+ | "temperature"
+ | "text";
+
+export interface UiMetricValue {
+ kind: UiMetricKind;
+ value: number | string;
+ unit?: string;
+ precision?: number;
+}
+
+export interface UiLink {
+ href: string;
+ label?: string;
+ external?: boolean;
+}
+
+export interface UiTelemetryCard {
+ id: string;
+ label: string;
+ value: UiMetricValue;
+ detail?: string;
+ description?: string;
+ icon?: string;
+ severity: UiSeverity;
+ progress?: number;
+ sparkline?: number[];
+}
+
+export interface UiServiceRow {
+ id: string;
+ label: string;
+ description: string;
+ icon?: string;
+ severity: UiSeverity;
+ detail?: string;
+ link?: UiLink;
+}
+
+export interface UiServiceGroup {
+ id: string;
+ title: string;
+ services: UiServiceRow[];
+ summary?: UiStatusItem[];
+}
+
+export interface UiStatusItem {
+ id: string;
+ label: string;
+ value: string;
+ severity?: UiSeverity;
+ link?: UiLink;
+}
+
+export interface UiModuleBlock {
+ id: string;
+ title?: string;
+ label?: string;
+ value?: string;
+ detail?: string;
+ icon?: string;
+ severity?: UiSeverity;
+}
+
+export interface UiDashboardPreview {
+ title: string;
+ subtitle?: string;
+ eyebrow?: string;
+ telemetry: UiTelemetryCard[];
+ serviceGroups: UiServiceGroup[];
+ modules: UiModuleBlock[];
+ statusItems: UiStatusItem[];
+}
diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte
index 68a3c90..5e495a8 100644
--- a/src/routes/+page.svelte
+++ b/src/routes/+page.svelte
@@ -1,83 +1,124 @@
- {data.dashboard.title} | Dimension Lab
+ {dashboard.title}
-
-
- {data.dashboard.subtitle}
- {data.dashboard.title}
- {data.dashboard.message}
-
- Runtime
- {data.dashboard.status}
-
-
-
+
+
+ {#each fixtureKeys as key}
+
+ {/each}
+
+
+
+
diff --git a/src/routes/page.test.ts b/src/routes/page.test.ts
new file mode 100644
index 0000000..5c9d0b7
--- /dev/null
+++ b/src/routes/page.test.ts
@@ -0,0 +1,28 @@
+import { render } from "svelte/server";
+import { describe, expect, test } from "vitest";
+import Page from "./+page.svelte";
+
+describe("home page design-system preview", () => {
+ test("renders the runtime dashboard data from the server load", () => {
+ const { body } = render(Page, {
+ props: {
+ data: {
+ dashboard: {
+ title: "Runtime Surface",
+ subtitle: "Loaded from persistence",
+ status: "building",
+ message: "Runtime model is available.",
+ schemaVersion: "dashboard.v1",
+ currentRevisionId: "revision-1234567890",
+ },
+ },
+ },
+ });
+
+ expect(body).toContain("Runtime Surface");
+ expect(body).toContain("Loaded from persistence");
+ expect(body).toContain("revision");
+ expect(body).toContain("primary");
+ expect(body).toContain("secondary");
+ });
+});