feat(ui): migrate dashboard runtime to React #28

Merged
vince merged 10 commits from codex/react-migration into main 2026-06-20 00:15:09 +02:00
3 changed files with 133 additions and 4 deletions
Showing only changes of commit 92a8990e7a - Show all commits

View file

@ -1,5 +1,11 @@
import { useEffect, useState } from "react";
import type { DashboardRuntimeState } from "$lib/server/dashboard";
import {
DashboardFrame,
SystemState,
dashboardDocumentToUiDashboard,
type UiSeverity,
} from "$lib/ui";
const loadingDashboardState: DashboardRuntimeState = {
state: "loading",
@ -14,14 +20,31 @@ export function AppStateView({
dashboard: DashboardRuntimeState;
}) {
if (dashboard.state === "ready") {
return <main>{dashboard.document.metadata.title}</main>;
return (
<DashboardFrame
dashboard={dashboardDocumentToUiDashboard(dashboard.document)}
/>
);
}
const detail = `${dashboard.subtitle}: ${dashboard.message}`;
const errors = dashboard.state === "invalid" ? dashboard.errors : [];
return (
<main className="state-shell" data-dashboard-state={dashboard.state}>
<h1>{dashboard.title}</h1>
<p>{dashboard.subtitle}</p>
<p>{dashboard.message}</p>
<SystemState
title={dashboard.title}
detail={detail}
severity={stateSeverity(dashboard.state)}
icon={stateIcon(dashboard.state)}
/>
{errors.length ? (
<ul aria-label="Validation errors">
{errors.map((error) => (
<li key={error}>{error}</li>
))}
</ul>
) : null}
</main>
);
}
@ -69,3 +92,15 @@ export default function App() {
return <AppStateView dashboard={dashboard} />;
}
function stateSeverity(state: DashboardRuntimeState["state"]): UiSeverity {
if (state === "invalid") return "danger";
if (state === "loading") return "loading";
return "stale";
}
function stateIcon(state: DashboardRuntimeState["state"]): string {
if (state === "invalid") return "mdi:file-alert-outline";
if (state === "loading") return "mdi:progress-clock";
return "mdi:tray";
}

View file

@ -133,6 +133,27 @@ a {
color: inherit;
}
.state-shell {
display: grid;
min-height: 100vh;
align-content: center;
gap: var(--ui-space-3);
padding: var(--ui-space-4);
}
.state-shell ul {
display: grid;
max-width: 56rem;
gap: var(--ui-space-2);
margin: 0;
border: var(--ui-border);
background: rgba(6, 8, 7, 0.82);
color: var(--ui-color-muted);
font-size: 0.76rem;
list-style-position: inside;
padding: var(--ui-space-3);
}
@media (prefers-reduced-motion: reduce) {
*,
*::before,

73
src/page.test.tsx Normal file
View file

@ -0,0 +1,73 @@
import { renderToString } from "react-dom/server";
import { describe, expect, test } from "vitest";
import { genericDashboardFixture } from "$lib/model/fixtures/generic";
import { AppStateView } from "./App";
describe("home page model renderer", () => {
test("renders the active dashboard model from the runtime state", () => {
const body = renderToString(
<AppStateView
dashboard={{
state: "ready",
document: genericDashboardFixture,
schemaVersion: "dashboard.v1",
currentRevisionId: "revision-1234567890",
}}
/>,
);
expect(body).toContain("Operations Console");
expect(body).toContain("Service Uptime");
expect(body).toContain("Identity");
expect(body).toContain("data-model-id=\"service-uptime\"");
expect(body).not.toContain("primary");
expect(body).not.toContain("secondary");
});
test("renders invalid model state without crashing", () => {
const body = renderToString(
<AppStateView
dashboard={{
state: "invalid",
title: "Invalid Dashboard",
subtitle: "Validation failed",
message: "Dashboard document is invalid.",
errors: ["/metadata/title is required"],
}}
/>,
);
expect(body).toContain("Invalid Dashboard");
expect(body).toContain("Validation failed");
expect(body).toContain("Dashboard document is invalid.");
expect(body).toContain("/metadata/title is required");
});
test("renders empty and loading model states without crashing", () => {
const empty = renderToString(
<AppStateView
dashboard={{
state: "empty",
title: "No Dashboard Model",
subtitle: "No active document",
message: "No validated dashboard document is active yet.",
}}
/>,
);
const loading = renderToString(
<AppStateView
dashboard={{
state: "loading",
title: "Loading Dashboard",
subtitle: "Fetching active model",
message: "Waiting for the active dashboard document.",
}}
/>,
);
expect(empty).toContain("No Dashboard Model");
expect(empty).toContain("No active document");
expect(loading).toContain("Loading Dashboard");
expect(loading).toContain("Fetching active model");
});
});