Compare commits

..

No commits in common. "main" and "codex/issue-2-sveltekit-runtime" have entirely different histories.

87 changed files with 260 additions and 16344 deletions

View file

@ -1,22 +0,0 @@
.git
.svelte-kit
build
coverage
data
dist
node_modules
out
playwright-report
storybook-static
test-results
.env
.env.*
apps/*/.turbo
apps/*/build
apps/*/data
apps/*/dist
apps/*/playwright-report
apps/*/test-results
packages/*/.turbo
packages/*/dist
packages/*/storybook-static

View file

@ -1,85 +0,0 @@
name: Dimension Lab website
on:
pull_request:
types:
- opened
- synchronize
- reopened
push:
branches:
- main
workflow_dispatch:
concurrency:
group: dimensionlab-website-${{ github.ref }}
cancel-in-progress: ${{ github.ref != 'refs/heads/main' }}
jobs:
ci:
runs-on: docker
timeout-minutes: 30
steps:
- name: Checkout
uses: https://data.forgejo.org/actions/checkout@v4
with:
fetch-depth: 0
submodules: false
- name: Initialize submodules
run: |
git config --global url."https://git.dimensionlab.net/".insteadOf "ssh://git@git.dimensionlab.net/"
git submodule update --init --recursive
- name: Install Bun
run: |
curl -fsSL https://bun.sh/install | bash -s "bun-v1.3.14"
"$HOME/.bun/bin/bun" --version
- name: Check, test, and build
run: |
export BUN_INSTALL="$HOME/.bun"
export PATH="$BUN_INSTALL/bin:$PATH"
bun install --frozen-lockfile
bun run check
bun run test
bun run build
deploy:
needs: ci
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
runs-on: deploy
timeout-minutes: 30
steps:
- name: Checkout
run: |
if [ -d .git ]; then
git remote set-url origin git@git.dimensionlab.net:vince/dimensionlab-website.git
else
git init
git remote add origin git@git.dimensionlab.net:vince/dimensionlab-website.git
fi
git fetch --force --prune --depth=1 origin "$GITHUB_SHA"
git checkout --force --detach "$GITHUB_SHA"
git clean -ffdx
- name: Initialize submodules
run: |
git config --global url."https://git.dimensionlab.net/".insteadOf "ssh://git@git.dimensionlab.net/"
git submodule update --init --recursive
- name: Verify Podman deployment socket
run: |
command -v podman
command -v systemctl
unit="$(timeout 15s podman inspect dimensionlab-website --format '{{ index .Config.Labels "PODMAN_SYSTEMD_UNIT" }}')"
test "$unit" = "dimensionlab-website.service"
- name: Deploy production website
env:
DEPLOY_CONTAINER_CLI: podman
DEPLOY_EVENT_NAME: ${{ github.event_name }}
DEPLOY_REF: ${{ github.ref }}
DEPLOY_RESTART_STRATEGY: quadlet-container
DEPLOY_SHA: ${{ github.sha }}
run: scripts/deploy-dimensionlab-website.sh

7
.gitignore vendored
View file

@ -1,12 +1,8 @@
node_modules/
out/
.svelte-kit/
build/
dist/
.vite/
.turbo/
apps/*/.turbo/
packages/*/.turbo/
.env
.env.*
@ -14,9 +10,6 @@ packages/*/.turbo/
data/*.sqlite
data/*.sqlite-*
apps/*/data/*.sqlite
apps/*/data/*.sqlite-*
coverage/
playwright-report/
test-results/
storybook-static/

4
.gitmodules vendored
View file

@ -1,4 +0,0 @@
[submodule "packages/ui"]
path = packages/ui
url = ssh://git@git.dimensionlab.net/vince/dimensionlab-ui.git
branch = main

186
README.md
View file

@ -1,187 +1,35 @@
# Dimension Lab Website
Turbo/Bun workspace for the Dimension Lab system overview dashboard and its
reusable React component library.
Standalone SvelteKit runtime for the Dimension Lab system overview dashboard.
This project is not a Homepage customization and does not depend on Homepage
runtime, frontend code, or configuration. The dashboard will be model-driven:
the reusable UI package stays content-free, the reusable dashboard model
package owns schema and validation, and environment-specific data lives in
validated dashboard model state inside the web app.
## Workspace Layout
- `apps/web`: Vite React website, Bun API server, model fixtures, Drizzle
persistence, Playwright e2e checks, and container build.
- `packages/dashboard-model`: reusable dashboard schema, validation, and
generic model fixtures shared by apps and tooling.
- `packages/ui`: Git submodule for the reusable dashboard React components,
design tokens, shadcn/radix primitives, generic fixtures, and Storybook.
Component source is grouped under `foundation`, `frames`, `operations`, and
`telemetry` domains.
- `docs/superpowers`: migration specs and execution plans used for this repo.
the reusable renderer stays content-free, while environment-specific data lives
in validated dashboard model state.
## Development
```sh
git submodule update --init --recursive
npm install
npm run dev
```
The current workspace has Bun available, so local verification can also use:
```sh
bun install
bun run dev
```
This MVP uses Drizzle with Bun SQLite for local file-backed persistence.
## Scripts
- `bun run dev`: start the web app dev runtime through Turbo.
- `bun run check`: run TypeScript checks in all workspaces.
- `bun run test`: run the unit test stage in all workspaces.
- `bun run test:unit`: run Vitest explicitly as the unit test stage.
- `bun run test:e2e`: build and run Playwright browser smoke and QA checks.
- `bun run test:qa`: run the release gate through Turbo across check, unit,
build, Storybook, and e2e tasks.
- `bun run build`: build the UI package, production website, and Bun server.
- `bun run preview`: preview the production web build.
- `bun run storybook`: start the UI package component explorer on port 6006.
- `bun run build-storybook`: build the UI package static Storybook artifact.
- `bun run db:generate`: generate web app Drizzle migrations.
- `bun run db:check`: validate web app migration consistency.
## Persistence
Dashboard documents are stored in SQLite through Drizzle. The default database
URL is:
```sh
DATABASE_URL=file:./data/dimensionlab.sqlite
```
SQLite files under `data/` and `apps/*/data/` are ignored. Drizzle schema lives
in `apps/web/src/lib/server/db/schema.ts`; tracked migrations live in
`apps/web/drizzle/`. Runtime startup applies the checked-in dashboard migrations
before reads or writes. If the app is launched from outside the web app tree,
set `DASHBOARD_MIGRATIONS_DIR` to the tracked migrations directory. The current
driver is `bun:sqlite`, which keeps this repo installable in the Bun workflow.
The store boundary is isolated so a later Postgres driver can replace the
SQLite connection without changing the dashboard model or renderer.
Stored dashboard documents pass through a version migration boundary before
reads or writes; the MVP supports `dashboard.v1` and fails unsupported versions
with an explicit migration error.
## Seed Data
The initial Dimension Lab dashboard lives in
`apps/web/src/lib/dashboard-seed/dimensionlab.ts` as validated model data. It
includes the first-screen telemetry, service groups, status strip, weather
module, Iconify icon identifiers, links, and datasource references. Values that
are not live yet are labeled as fallback values in the data so later datasource
adapters can replace them without changing presentation components.
- `npm run dev`: start the local development server.
- `npm run check`: run Svelte and TypeScript checks.
- `npm run build`: build the production app.
- `npm run preview`: preview the production build.
## Runtime Shape
The browser app in `apps/web` is built with Vite and React. Local development
starts Vite for HMR and a loopback Bun API server for `/api/*` routes.
Production uses a small Bun HTTP server at `apps/web/build/index.js` to serve
the Vite `apps/web/dist/` assets and JSON API routes. The current persistence
runtime is Bun because the MVP SQLite driver is `bun:sqlite`.
## Storybook
Storybook lives with `packages/ui` and covers the reusable UI components with
generic fixtures only. Stories must not import environment-specific dashboard
content; the presentation layer accepts labels, values, icons, status, and links
through typed props.
## MVP QA Gate
Install the Chromium browser once before running e2e checks locally:
```sh
bunx playwright install chromium
```
Run the CI-ready release gate with:
```sh
bun run test:qa
```
The gate runs TypeScript checks, Vitest coverage for model,
persistence, renderer, datasource mocks, and presentation boundaries, the
production build, the static Storybook build, and Playwright desktop/mobile
smoke checks against the built adapter output. Turbo owns the release task
graph; app package scripts stay as leaf commands and do not re-run the QA
pipeline internally. Playwright also performs
baseline screenshot checks, keyboard navigation checks, reduced-motion checks,
landmark checks, and axe accessibility checks against the real model-driven
route.
Playwright uses an isolated SQLite database per run unless
`PLAYWRIGHT_DATABASE_URL` is set explicitly.
Presentation code is checked for Dimension Lab content leakage. Environment
specific labels, links, icon names, fallback values, and datasource references
belong in validated model data, not reusable components.
## Deployment Notes
The production build emits Vite client assets under `apps/web/dist/` and a Bun
server entry at `apps/web/build/index.js`. A minimal deployment flow is:
```sh
git submodule update --init --recursive
bun install --frozen-lockfile
bun run build
cd apps/web
DATABASE_URL=file:/data/dimensionlab.sqlite HOST=0.0.0.0 PORT=3000 bun build/index.js
```
Mount `/data` or set `DATABASE_URL` to another persistent SQLite path. If the
process starts outside the repository root, set `DASHBOARD_MIGRATIONS_DIR` to
the checked-in `apps/web/drizzle/` directory so startup migrations can run.
### Internal Container
The checked-in `apps/web/Containerfile` runs
`turbo prune @dimensionlab/web --docker`, installs the pruned manifest set, and
builds the React client plus Bun server from the pruned workspace source. For the
Dimension Lab internal host, run it behind Caddy on a loopback port and mount
persistent state at `/data`:
```sh
podman build -f apps/web/Containerfile -t localhost/dimensionlab-website:latest .
podman run --rm \
--publish 127.0.0.1:25341:3000 \
--volume "$HOME/containers/dimensionlab-website/data:/data:Z" \
--env-file "$HOME/containers/dimensionlab-website/dimensionlab-website.env" \
localhost/dimensionlab-website:latest
```
The env file must provide `AGENT_CONFIG_TOKEN`. Runtime defaults inside the
image set `HOST=0.0.0.0`, `PORT=3000`,
`DATABASE_URL=file:/data/dimensionlab.sqlite`, and
`DASHBOARD_MIGRATIONS_DIR=/repo/apps/web/drizzle`.
### Forgejo Actions Deployment
Merges to `main` run `.forgejo/workflows/dimensionlab-website.yml`. Pull
requests run check, test, and build only; the deploy job is guarded to run only
for `push` events on `refs/heads/main`.
The workflow uses two runner classes. Pull request CI runs on the containerized
`docker` runner. Production deployment runs on a separate host runner with the
`deploy:host` label so the guarded deploy script can use the user's rootless
`podman` and `systemctl --user` commands directly. The deploy job uses a
shell-only `git fetch` checkout so the host runner does not need a Node runtime
for checkout actions.
```yaml
runner:
labels:
- deploy:host
```
The deploy job also performs a host preflight against the
`dimensionlab-website.service` Podman label before it builds or restarts the
production container. Do not give the general pull request runner deployment
socket access; keep deploy privileges on the dedicated `deploy` runner.
The first deployment target is a Node/container-friendly SvelteKit build. Later
issues add the versioned dashboard model, SQLite persistence, component system,
Storybook, seed data, and rendering pipeline.

View file

@ -1,39 +0,0 @@
FROM docker.io/oven/bun:1.3.14 AS base
WORKDIR /repo
ENV PATH=/repo/apps/web/node_modules/.bin:/repo/packages/dashboard-model/node_modules/.bin:/repo/packages/ui/node_modules/.bin:/repo/node_modules/.bin:$PATH
FROM base AS pruner
COPY . .
RUN bunx turbo prune @dimensionlab/web --docker
FROM base AS deps
COPY --from=pruner /repo/out/json/ ./
RUN bun install --frozen-lockfile --ignore-scripts
FROM deps AS build
COPY --from=pruner /repo/out/full/ ./
COPY --from=pruner /repo/tsconfig.base.json /repo/tsconfig.json ./
RUN bun run build
FROM base AS runtime
WORKDIR /repo/apps/web
ENV NODE_ENV=production
ENV HOST=0.0.0.0
ENV PORT=3000
ENV DATABASE_URL=file:/data/dimensionlab.sqlite
ENV DASHBOARD_MIGRATIONS_DIR=/repo/apps/web/drizzle
COPY --from=build /repo/apps/web/build ./build
COPY --from=build /repo/apps/web/dist ./dist
COPY --from=build /repo/apps/web/drizzle ./drizzle
RUN mkdir -p /data
VOLUME ["/data"]
EXPOSE 3000
CMD ["bun", "build/index.js"]

View file

@ -1,10 +0,0 @@
import { defineConfig } from "drizzle-kit";
export default defineConfig({
schema: "./src/lib/server/db/schema.ts",
out: "./drizzle",
dialect: "sqlite",
dbCredentials: {
url: process.env.DATABASE_URL?.replace(/^file:/, "") || "./data/dimensionlab.sqlite",
},
});

View file

@ -1,20 +0,0 @@
CREATE TABLE `dashboard_documents` (
`id` text PRIMARY KEY NOT NULL,
`current_revision_id` text,
`created_at` integer NOT NULL,
`updated_at` integer NOT NULL
);
--> statement-breakpoint
CREATE TABLE `dashboard_revisions` (
`id` text PRIMARY KEY NOT NULL,
`dashboard_id` text NOT NULL,
`schema_version` text NOT NULL,
`document` text NOT NULL,
`actor` text NOT NULL,
`message` text,
`operation` text NOT NULL,
`source_revision_id` text,
`created_at` integer NOT NULL
);
--> statement-breakpoint
CREATE INDEX `idx_dashboard_revisions_dashboard_created` ON `dashboard_revisions` (`dashboard_id`,`created_at`);

View file

@ -1,138 +0,0 @@
{
"version": "6",
"dialect": "sqlite",
"id": "00000000-0000-0000-0000-dashboard0000",
"prevId": "00000000-0000-0000-0000-000000000000",
"tables": {
"dashboard_documents": {
"name": "dashboard_documents",
"columns": {
"id": {
"name": "id",
"type": "text",
"primaryKey": true,
"notNull": true,
"autoincrement": false
},
"current_revision_id": {
"name": "current_revision_id",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"created_at": {
"name": "created_at",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"updated_at": {
"name": "updated_at",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"checkConstraints": {}
},
"dashboard_revisions": {
"name": "dashboard_revisions",
"columns": {
"id": {
"name": "id",
"type": "text",
"primaryKey": true,
"notNull": true,
"autoincrement": false
},
"dashboard_id": {
"name": "dashboard_id",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"schema_version": {
"name": "schema_version",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"document": {
"name": "document",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"actor": {
"name": "actor",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"message": {
"name": "message",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"operation": {
"name": "operation",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"source_revision_id": {
"name": "source_revision_id",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"created_at": {
"name": "created_at",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false
}
},
"indexes": {
"idx_dashboard_revisions_dashboard_created": {
"name": "idx_dashboard_revisions_dashboard_created",
"columns": [
"dashboard_id",
"created_at"
],
"isUnique": false
}
},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"checkConstraints": {}
}
},
"enums": {},
"views": {},
"_meta": {
"columns": {},
"schemas": {},
"tables": {}
},
"internal": {
"indexes": {}
}
}

View file

@ -1,13 +0,0 @@
{
"version": "7",
"dialect": "sqlite",
"entries": [
{
"idx": 0,
"version": "6",
"when": 1781805600000,
"tag": "0000_dashboard_persistence",
"breakpoints": true
}
]
}

View file

@ -1,22 +0,0 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="description" content="Dimension Lab dashboard runtime" />
<title>Dimension Lab</title>
<script>
try {
const theme = localStorage.getItem("dashboard-ui-theme");
document.documentElement.dataset.uiTheme =
theme === "light" ? "light" : "dark";
} catch {
document.documentElement.dataset.uiTheme = "dark";
}
</script>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>

View file

@ -1,51 +0,0 @@
{
"name": "@dimensionlab/web",
"version": "0.0.1",
"private": true,
"type": "module",
"scripts": {
"dev": "bun src/server/dev.ts",
"build": "rm -rf build && vite build && bun build src/server/index.ts --target bun --outdir build",
"preview": "HOST=0.0.0.0 PORT=4173 bun build/index.js",
"check": "tsc --noEmit",
"test": "bun --bun vitest run",
"test:unit": "bun --bun vitest run",
"test:e2e": "env -u NO_COLOR playwright test",
"db:generate": "drizzle-kit generate",
"db:check": "drizzle-kit check"
},
"dependencies": {
"@dimensionlab/dashboard-model": "workspace:*",
"@dimensionlab/ui": "workspace:*",
"@sinclair/typebox": "^0.34.49",
"ajv": "^8.20.0",
"ajv-formats": "^3.0.1",
"drizzle-orm": "^0.45.2",
"react": "^19.2.7",
"react-dom": "^19.2.7",
"tw-animate-css": "^1.4.0"
},
"devDependencies": {
"@axe-core/playwright": "^4.11.3",
"@playwright/test": "^1.61.0",
"@tailwindcss/vite": "^4.3.1",
"@types/bun": "^1.3.14",
"@types/node": "^25.9.3",
"@types/react": "^19.2.17",
"@types/react-dom": "^19.2.3",
"@vitejs/plugin-react": "^6.0.2",
"bun-types": "^1.3.14",
"drizzle-kit": "^0.31.10",
"msw": "^2.14.6",
"shadcn": "^4.11.0",
"tailwindcss": "^4.3.1",
"typescript": "^6.0.3",
"vite": "^8.0.16",
"vitest": "^4.1.9"
},
"msw": {
"workerDirectory": [
"static"
]
}
}

View file

@ -1,51 +0,0 @@
import { defineConfig, devices } from "@playwright/test";
const port = Number(process.env.PLAYWRIGHT_PORT || 4173);
const baseURL = `http://127.0.0.1:${port}`;
const storybookPort = Number(process.env.PLAYWRIGHT_STORYBOOK_PORT || 6007);
const storybookURL = `http://127.0.0.1:${storybookPort}`;
const databaseUrl =
process.env.PLAYWRIGHT_DATABASE_URL ||
`file:./data/playwright-${process.pid}-${Date.now()}.sqlite`;
export default defineConfig({
testDir: "tests/e2e",
fullyParallel: true,
forbidOnly: !!process.env.CI,
retries: process.env.CI ? 2 : 0,
reporter: process.env.CI ? [["github"], ["list"]] : "list",
use: {
baseURL,
trace: "retain-on-failure",
screenshot: "only-on-failure",
},
webServer: [
{
command: `DISABLE_LIVE_DATASOURCES=1 DATABASE_URL=${databaseUrl} HOST=127.0.0.1 PORT=${port} bun build/index.js`,
url: baseURL,
reuseExistingServer: false,
timeout: 120_000,
},
{
command: `cd ../.. && STORYBOOK_STATIC_PORT=${storybookPort} bun apps/web/tests/e2e/storybook-server.ts`,
url: storybookURL,
reuseExistingServer: false,
timeout: 120_000,
},
],
projects: [
{
name: "chromium-desktop",
use: {
...devices["Desktop Chrome"],
viewport: { width: 1440, height: 1000 },
},
},
{
name: "chromium-mobile",
use: {
...devices["Pixel 7"],
},
},
],
});

View file

@ -1,238 +0,0 @@
import { renderToString } from "react-dom/server";
import { describe, expect, test } from "vitest";
import { genericDashboardFixture } from "@dimensionlab/dashboard-model/fixtures";
import { dimensionLabDashboardFixture } from "$lib/dashboard-seed/dimensionlab";
import {
AppStateView,
dashboardTileMatchKey,
dashboardHydrationTiles,
restoreDashboardTileSnapshots,
} from "./App";
describe("React app dashboard state view", () => {
test("renders loading dashboard state", () => {
const html = renderToString(
<AppStateView
dashboard={{
state: "loading",
title: "Loading Dashboard",
subtitle: "Fetching active model",
message: "Waiting for the active dashboard document.",
}}
/>,
);
expect(html).toContain("Loading Dashboard");
expect(html).toContain("Fetching active model");
});
test("renders an accessible theme toggle with the active theme", () => {
const html = renderToString(
<AppStateView
dashboard={{
state: "loading",
title: "Loading Dashboard",
subtitle: "Fetching active model",
message: "Waiting for the active dashboard document.",
}}
theme="dark"
onThemeChange={() => undefined}
/>,
);
expect(html).toContain('data-ui-theme-toggle="true"');
expect(html).toContain('data-ui-theme-current="dark"');
expect(html).toContain('aria-label="Light theme"');
expect(html).toContain('aria-pressed="false"');
expect(html).toContain("Theme");
expect(html).toContain("Dark");
expect(html).toContain("Light");
});
test("renders the dashboard shell while individual items hydrate", () => {
const html = renderToString(
<AppStateView
dashboard={{
state: "ready",
document: genericDashboardFixture,
schemaVersion: "dashboard.v1",
currentRevisionId: "revision-1234567890",
}}
hydratingItemIds={new Set([
"telemetry:service-uptime",
"service:core-services:identity",
"module:ambient",
"status:runtime:status",
])}
/>,
);
expect(html).toContain("Operations Console");
expect(html).toContain("Service Uptime");
expect(html).toContain("Identity");
expect(html).toContain("Environment");
expect(html).not.toContain("Loading Dashboard");
expect(html).toContain(
'data-severity="loading" data-model-id="service-uptime"',
);
expect(html).toContain('data-severity="loading" data-model-id="identity"');
expect(html).toContain('data-severity="loading" data-model-id="ambient"');
expect(html).toContain('data-severity="loading" data-model-id="runtime:status"');
});
test("hydrates every status cell from the Dimension Lab shell", () => {
expect(dashboardHydrationTiles(dimensionLabDashboardFixture)).toContainEqual({
kind: "status",
stripId: "footer-status",
id: "auto-refresh",
});
});
test("prioritizes status, telemetry, modules, then services for hydration", () => {
const tiles = dashboardHydrationTiles(dimensionLabDashboardFixture);
const kinds = tiles.map((tile) => tile.kind);
const firstServiceIndex = kinds.indexOf("service");
expect(kinds.slice(0, 5)).toEqual([
"status",
"status",
"status",
"status",
"status",
]);
expect(kinds.lastIndexOf("telemetry")).toBeLessThan(kinds.indexOf("module"));
expect(kinds.lastIndexOf("module")).toBeLessThan(firstServiceIndex);
expect(
tiles.filter((tile) => tile.kind === "telemetry").map((tile) => tile.id),
).toEqual(
dimensionLabDashboardFixture.telemetry
.filter((card) => card.datasource?.type === "external")
.map((card) => card.id),
);
expect(
tiles.filter((tile) => tile.kind === "service").map((tile) => ({
groupId: tile.groupId,
id: tile.id,
})),
).toEqual(
dimensionLabDashboardFixture.serviceGroups.flatMap((group) =>
group.services
.filter((service) => service.datasource?.type === "external")
.map((service) => ({
groupId: group.id,
id: service.id,
}))
),
);
});
test("applies restored tile snapshots without marking them as loading", () => {
const restored = restoreDashboardTileSnapshots(
{
state: "ready",
document: dimensionLabDashboardFixture,
schemaVersion: "dashboard.v1",
currentRevisionId: "revision-a",
},
[
{
ageMs: 60_000,
response: {
state: "ready",
tile: { kind: "telemetry", id: "infra-ram" },
item: {
...dimensionLabDashboardFixture.telemetry[0],
detail: "cached - stale 60s",
severity: "stale",
},
},
},
],
);
expect(restored.restoredItemIds).toEqual(new Set(["telemetry:infra-ram"]));
if (restored.dashboard.state !== "ready") {
throw new Error("Expected dashboard to be ready");
}
expect(restored.dashboard.document.telemetry[0]).toMatchObject({
id: "infra-ram",
detail: "cached - stale 60s",
severity: "stale",
});
});
test("does not restore aggregate health snapshots over the fresh shell", () => {
const restored = restoreDashboardTileSnapshots(
{
state: "ready",
document: dimensionLabDashboardFixture,
schemaVersion: "dashboard.v1",
currentRevisionId: "revision-a",
},
[
{
ageMs: 60_000,
response: {
state: "ready",
tile: { kind: "status", stripId: "footer-status", id: "system-status" },
item: {
id: "system-status",
label: "System Status",
value: "20 services down",
severity: "stale",
},
},
},
{
ageMs: 60_000,
response: {
state: "ready",
tile: { kind: "module", id: "runtime-health-summary" },
item: {
id: "runtime-health-summary",
kind: "summary",
title: "Runtime Health",
value: "20 services down",
detail: "0 warnings - 8 services ok - stale 60s",
severity: "stale",
},
},
},
],
);
expect(restored.restoredItemIds).toEqual(new Set());
if (restored.dashboard.state !== "ready") {
throw new Error("Expected dashboard to be ready");
}
expect(
restored.dashboard.document.statusStrips[0].items.find((item) =>
item.id === "system-status"
),
).toMatchObject({
id: "system-status",
value: "Fallback operational",
});
expect(
restored.dashboard.document.modules?.find((module) =>
module.id === "runtime-health-summary"
),
).toMatchObject({
id: "runtime-health-summary",
value: "fallback",
});
});
test("uses structured tile match keys for delimiter-bearing ids", () => {
expect(
dashboardTileMatchKey({ kind: "service", groupId: "a:b", id: "c" }),
).not.toBe(
dashboardTileMatchKey({ kind: "service", groupId: "a", id: "b:c" }),
);
expect(
dashboardTileMatchKey({ kind: "status", stripId: "a:b", id: "c" }),
).not.toBe(
dashboardTileMatchKey({ kind: "status", stripId: "a", id: "b:c" }),
);
});
});

View file

@ -1,875 +0,0 @@
import { useEffect, useState } from "react";
import type {
DashboardDocument,
DashboardModule,
ServiceEntry,
StatusItem,
TelemetryCard,
} from "@dimensionlab/dashboard-model";
import type { DashboardRuntimeState } from "$lib/server/dashboard";
import {
attachDashboardRefreshLifecycle,
collectVisibleDashboardModelIds,
createDashboardPerformanceMarks,
createDashboardRefreshDelay,
createDashboardTileSnapshotStore,
createDashboardTileBackoff,
createDashboardRequestAborter,
isPersistableDashboardTileSnapshot,
runViewportAwareDashboardHydrationQueue,
shouldPauseDashboardRefresh,
subscribeToDashboardTileEvents,
waitForDashboardHydrationIdle,
type DashboardIntersectionObserverFactory,
type DashboardTileReference,
type DashboardTileSnapshotStoreContext,
type RestoredDashboardTileSnapshot,
} from "$lib/client/dashboard-refresh";
import { dashboardDocumentToUiDashboard } from "$lib/ui-adapter/model-renderer";
import {
DashboardFrame,
SystemState,
ThemeToggle,
persistUiTheme,
resolveInitialUiTheme,
type UiTheme,
type UiSeverity,
type UiDashboardPreview,
} from "@dimensionlab/ui";
type DashboardTileResponse =
| {
state: "ready";
tile: DashboardTileReference;
item: DashboardModule | ServiceEntry | StatusItem | TelemetryCard;
}
| {
state: "not_found";
tile: DashboardTileReference;
message: string;
}
| {
state: "disabled";
tile: DashboardTileReference;
message: string;
};
type DashboardTilesBatchResponse = {
state: "ready";
tiles: DashboardTileResponse[];
};
type DashboardTileHydrationResult = "aborted" | "failed" | "ready";
const dashboardTileHydrationConcurrency = 6;
const dashboardFallbackRefreshIntervalMs = 30_000;
const dashboardViewportObservationTimeoutMs = 80;
type DashboardTileSnapshotStore = ReturnType<typeof createDashboardTileSnapshotStore>;
export function AppStateView({
dashboard,
onThemeChange,
theme,
hydratingItemIds,
}: {
dashboard: DashboardRuntimeState;
hydratingItemIds?: ReadonlySet<string>;
onThemeChange?: (theme: UiTheme) => void;
theme?: UiTheme;
}) {
const themeToggle =
theme && onThemeChange ? (
<ThemeToggle theme={theme} onThemeChange={onThemeChange} />
) : null;
if (dashboard.state === "ready") {
const uiDashboard = markHydratingItems(
dashboardDocumentToUiDashboard(dashboard.document),
hydratingItemIds,
);
return (
<DashboardFrame
dashboard={uiDashboard}
actions={themeToggle}
/>
);
}
const detail = `${dashboard.subtitle}: ${dashboard.message}`;
const errors = dashboard.state === "invalid" ? dashboard.errors : [];
return (
<main className="state-shell" data-dashboard-state={dashboard.state}>
{themeToggle ? (
<div className="state-shell__actions">{themeToggle}</div>
) : null}
<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>
);
}
export function resolveDocumentMetadata(dashboard: DashboardRuntimeState): {
description: string;
title: string;
} {
if (dashboard.state === "ready") {
const uiDashboard = dashboardDocumentToUiDashboard(dashboard.document);
return {
title: uiDashboard.title,
description:
uiDashboard.subtitle ||
dashboard.document.metadata.description ||
uiDashboard.title,
};
}
return {
title: dashboard.title,
description: dashboard.subtitle || dashboard.message,
};
}
export default function App() {
const [dashboard, setDashboard] =
useState<DashboardRuntimeState | null>(null);
const [hydratingItemIds, setHydratingItemIds] = useState<Set<string>>(
() => new Set(),
);
const [theme, setTheme] = useState<UiTheme>(() => {
if (typeof window === "undefined") return "dark";
return resolveInitialUiTheme(getThemeStorage());
});
useEffect(() => {
if (!dashboard) return;
const metadata = resolveDocumentMetadata(dashboard);
document.title = metadata.title;
let description = document.querySelector<HTMLMetaElement>(
'meta[name="description"]',
);
if (!description) {
description = document.createElement("meta");
description.name = "description";
document.head.append(description);
}
description.content = metadata.description;
}, [dashboard]);
useEffect(() => {
document.documentElement.dataset.uiTheme = theme;
persistUiTheme(theme, getThemeStorage());
}, [theme]);
useEffect(() => {
let cancelled = false;
let refreshTimer: number | undefined;
let unsubscribeTileEvents: (() => void) | undefined;
let lastRefreshIntervalMs = dashboardFallbackRefreshIntervalMs;
let hydrationRun = 0;
const requestAborter = createDashboardRequestAborter();
const performanceMarks = createDashboardPerformanceMarks();
const refreshDelay = createDashboardRefreshDelay();
const tileBackoff = createDashboardTileBackoff();
const tileSnapshotStore = createDashboardTileSnapshotStore(
getTileSnapshotStorage(),
);
function refreshPaused() {
return shouldPauseDashboardRefresh({
visibilityState: document.visibilityState,
online: navigator.onLine,
});
}
function clearRefreshTimer() {
if (refreshTimer !== undefined) {
window.clearTimeout(refreshTimer);
refreshTimer = undefined;
}
}
function scheduleNextRefresh(baseDelayMs: number) {
clearRefreshTimer();
if (cancelled || refreshPaused()) return;
refreshTimer = window.setTimeout(() => {
refreshTimer = undefined;
void loadDashboard();
}, refreshDelay.nextDelayMs(baseDelayMs));
}
function pauseRefreshes() {
clearRefreshTimer();
unsubscribeTileEvents?.();
unsubscribeTileEvents = undefined;
requestAborter.abortActiveRequests();
setHydratingItemIds(new Set());
}
async function loadDashboard() {
if (cancelled || refreshPaused()) return;
clearRefreshTimer();
const shellSignal = requestAborter.beginShellRun();
try {
const response = await fetch("/api/dashboard", { signal: shellSignal });
const nextDashboard = (await response.json()) as DashboardRuntimeState;
if (cancelled || shellSignal.aborted || refreshPaused()) return;
refreshDelay.recordSuccess();
performanceMarks.markShellLoad();
const restored = restoreDashboardTileSnapshots(
nextDashboard,
nextDashboard.state === "ready"
? tileSnapshotStore.restore({
currentRevisionId: nextDashboard.currentRevisionId,
schemaVersion: nextDashboard.schemaVersion,
})
: [],
);
setDashboard(restored.dashboard);
const currentRun = ++hydrationRun;
if (
restored.dashboard.state === "ready" &&
restored.dashboard.liveDatasourceHydration?.enabled !== false
) {
const tiles = dashboardHydrationTiles(restored.dashboard.document).filter(
(tile) => tileBackoff.canAttempt(dashboardTileKey(tile)),
);
const tileSignal = requestAborter.beginTileRun();
setHydratingItemIds(
new Set(
tiles
.map(dashboardTileKey)
.filter((key) => !restored.restoredItemIds.has(key)),
),
);
hydrateDashboardTiles(
tiles,
currentRun,
tileSignal,
{
currentRevisionId: restored.dashboard.currentRevisionId,
schemaVersion: restored.dashboard.schemaVersion,
},
);
subscribeDashboardTileEvents(
currentRun,
tileSignal,
{
currentRevisionId: restored.dashboard.currentRevisionId,
schemaVersion: restored.dashboard.schemaVersion,
},
);
} else {
setHydratingItemIds(new Set());
}
const refreshIntervalSeconds =
nextDashboard.state === "ready"
? nextDashboard.document.metadata.refreshIntervalSeconds
: undefined;
if (refreshIntervalSeconds && !refreshPaused()) {
lastRefreshIntervalMs = refreshIntervalSeconds * 1000;
scheduleNextRefresh(lastRefreshIntervalMs);
}
} catch (error) {
if (!isAbortError(error) && !cancelled) {
refreshDelay.recordFailure();
console.error("Dashboard refresh failed", error);
scheduleNextRefresh(lastRefreshIntervalMs);
}
}
}
function hydrateDashboardTiles(
tiles: DashboardTileReference[],
run: number,
signal: AbortSignal,
snapshotContext: DashboardTileSnapshotStoreContext,
) {
const modelIds = tiles.map(dashboardTileModelId);
void runViewportAwareDashboardHydrationQueue({
batchSize: dashboardTileHydrationConcurrency,
collectVisibleModelIds: async () => {
await waitForDashboardRenderFrame(signal);
return collectVisibleDashboardModelIds({
clearTimeout: window.clearTimeout.bind(window),
createObserver: getDashboardIntersectionObserverFactory(),
documentTarget: document,
modelIds,
setTimeout: window.setTimeout.bind(window),
signal,
timeoutMs: dashboardViewportObservationTimeoutMs,
});
},
concurrency: dashboardTileHydrationConcurrency,
getModelId: dashboardTileModelId,
hydrate: async (tile) => {
const key = dashboardTileKey(tile);
const result = await hydrateDashboardTile(
tile,
run,
signal,
snapshotContext,
);
if (result === "ready") {
tileBackoff.recordSuccess(key);
} else if (result === "failed") {
tileBackoff.recordFailure(key);
}
},
hydrateBatch: async (batch) => {
const results = await hydrateDashboardTileBatch(
batch,
run,
signal,
snapshotContext,
);
for (const { result, tile } of results) {
const key = dashboardTileKey(tile);
if (result === "ready") {
tileBackoff.recordSuccess(key);
} else if (result === "failed") {
tileBackoff.recordFailure(key);
}
}
},
items: tiles,
onAllItemsSettled: () => performanceMarks.markAllTilesSettled(),
onVisibleItemsSettled: () => performanceMarks.markVisibleTilesReady(),
signal,
waitForIdle: () =>
waitForDashboardHydrationIdle({
...getDashboardIdleCallbacks(),
clearTimeout: window.clearTimeout.bind(window),
setTimeout: window.setTimeout.bind(window),
signal,
}),
});
}
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,
signal: AbortSignal,
snapshotContext: DashboardTileSnapshotStoreContext,
): Promise<Array<{ result: DashboardTileHydrationResult; tile: DashboardTileReference }>> {
try {
const response = await fetch("/api/dashboard/tiles", {
body: JSON.stringify({ tiles }),
headers: { "Content-Type": "application/json" },
method: "POST",
signal,
});
const batchResponse = (await response.json()) as DashboardTilesBatchResponse;
if (
cancelled ||
signal.aborted ||
run !== hydrationRun ||
!response.ok ||
batchResponse.state !== "ready"
) {
throw new Error("Dashboard tile batch hydration failed");
}
const responsesByKey = new Map(
batchResponse.tiles.map((tileResponse) => [
dashboardTileMatchKey(tileResponse.tile),
tileResponse,
]),
);
return tiles.map((tile) => {
const key = dashboardTileKey(tile);
try {
return {
tile,
result: applyDashboardTileHydrationResponse(
tile,
responsesByKey.get(dashboardTileMatchKey(tile)),
run,
signal,
snapshotContext,
),
};
} finally {
finishDashboardTileHydration(key, run, signal);
}
});
} catch (error) {
if (signal.aborted || cancelled || run !== hydrationRun) {
return tiles.map((tile) => ({ result: "aborted", tile }));
}
return Promise.all(
tiles.map(async (tile) => ({
tile,
result: await hydrateDashboardTile(tile, run, signal, snapshotContext),
})),
);
}
}
async function hydrateDashboardTile(
tile: DashboardTileReference,
run: number,
signal: AbortSignal,
snapshotContext: DashboardTileSnapshotStoreContext,
): Promise<DashboardTileHydrationResult> {
const key = dashboardTileKey(tile);
try {
const response = await fetch(dashboardTileUrl(tile), { signal });
const tileResponse = (await response.json()) as DashboardTileResponse;
if (!response.ok) {
return "failed";
}
const result = applyDashboardTileHydrationResponse(
tile,
tileResponse,
run,
signal,
snapshotContext,
);
if (result !== "ready") {
return signal.aborted || cancelled || run !== hydrationRun
? "aborted"
: "failed";
}
return "ready";
} catch (error) {
if (!isAbortError(error) && !cancelled) {
console.error("Dashboard tile hydration failed", error);
return "failed";
}
return "aborted";
} finally {
finishDashboardTileHydration(key, run, signal);
}
}
function applyDashboardTileHydrationResponse(
tile: DashboardTileReference,
tileResponse: DashboardTileResponse | undefined,
run: number,
signal: AbortSignal,
snapshotContext: DashboardTileSnapshotStoreContext,
): DashboardTileHydrationResult {
if (
cancelled ||
signal.aborted ||
run !== hydrationRun ||
!tileResponse ||
dashboardTileMatchKey(tileResponse.tile) !== dashboardTileMatchKey(tile) ||
tileResponse.state !== "ready"
) {
return signal.aborted || cancelled || run !== hydrationRun
? "aborted"
: "failed";
}
const readyTileResponse = tileResponse;
setDashboard((current) =>
current?.state === "ready"
? {
...current,
document: applyDashboardTile(current.document, readyTileResponse),
}
: current,
);
tileSnapshotStore.saveReadyTile({
...snapshotContext,
response: readyTileResponse,
});
performanceMarks.markFirstTileReady();
return "ready";
}
function finishDashboardTileHydration(
key: string,
run: number,
signal: AbortSignal,
) {
if (!cancelled && !signal.aborted && run === hydrationRun) {
setHydratingItemIds((current) => {
const next = new Set(current);
next.delete(key);
return next;
});
}
}
const detachRefreshLifecycle = attachDashboardRefreshLifecycle({
documentTarget: document,
windowTarget: window,
loadDashboard,
pauseRefreshes,
refreshPaused: () => cancelled || refreshPaused(),
});
void loadDashboard();
return () => {
cancelled = true;
clearRefreshTimer();
unsubscribeTileEvents?.();
requestAborter.abortActiveRequests();
detachRefreshLifecycle();
};
}, []);
return dashboard ? (
<AppStateView
dashboard={dashboard}
hydratingItemIds={hydratingItemIds}
theme={theme}
onThemeChange={setTheme}
/>
) : null;
}
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";
}
function getThemeStorage(): Storage | undefined {
try {
return window.localStorage;
} catch {
return undefined;
}
}
function getTileSnapshotStorage(): Storage | undefined {
try {
return window.localStorage;
} catch {
return undefined;
}
}
type DashboardIdleWindow = Window & {
cancelIdleCallback?: (handle: number) => void;
requestIdleCallback?: (
callback: () => void,
options?: { timeout?: number },
) => number;
};
function getDashboardIdleCallbacks() {
const idleWindow = window as DashboardIdleWindow;
return {
cancelIdleCallback: idleWindow.cancelIdleCallback?.bind(idleWindow),
requestIdleCallback: idleWindow.requestIdleCallback?.bind(idleWindow),
};
}
function getDashboardIntersectionObserverFactory():
| DashboardIntersectionObserverFactory
| undefined {
if (typeof window.IntersectionObserver === "undefined") return undefined;
return (callback) =>
new window.IntersectionObserver((entries) => {
callback(entries);
});
}
async function waitForDashboardRenderFrame(signal: AbortSignal): Promise<void> {
if (signal.aborted) return;
await new Promise<void>((resolve) => {
let settled = false;
let frame: number | undefined;
function finish() {
if (settled) return;
settled = true;
if (frame !== undefined) window.cancelAnimationFrame(frame);
signal.removeEventListener("abort", finish);
resolve();
}
signal.addEventListener("abort", finish, { once: true });
frame = window.requestAnimationFrame(finish);
});
}
function isAbortError(error: unknown): boolean {
return (
typeof error === "object" &&
error !== null &&
"name" in error &&
error.name === "AbortError"
);
}
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[],
): {
dashboard: DashboardRuntimeState;
restoredItemIds: Set<string>;
} {
if (dashboard.state !== "ready" || snapshots.length === 0) {
return {
dashboard,
restoredItemIds: new Set(),
};
}
return snapshots.filter((snapshot) =>
isPersistableDashboardTileSnapshot(snapshot.response.tile)
).reduce(
(current, snapshot) => ({
dashboard: {
...current.dashboard,
document: applyDashboardTile(
current.dashboard.document,
snapshot.response as Extract<DashboardTileResponse, { state: "ready" }>,
),
},
restoredItemIds: new Set([
...current.restoredItemIds,
dashboardTileKey(snapshot.response.tile),
]),
}),
{
dashboard,
restoredItemIds: new Set<string>(),
},
);
}
function markHydratingItems(
dashboard: UiDashboardPreview,
hydratingItemIds?: ReadonlySet<string>,
): UiDashboardPreview {
if (!hydratingItemIds?.size) return dashboard;
return {
...dashboard,
telemetry: dashboard.telemetry.map((card) =>
hydratingItemIds.has(`telemetry:${card.id}`)
? {
...card,
severity: "loading",
detail: "loading live telemetry",
}
: card,
),
serviceGroups: dashboard.serviceGroups.map((group) => ({
...group,
services: group.services.map((service) =>
hydratingItemIds.has(`service:${group.id}:${service.id}`)
? {
...service,
severity: "loading",
detail: "loading",
}
: service,
),
})),
modules: dashboard.modules.map((module) =>
hydratingItemIds.has(`module:${module.id}`)
? {
...module,
severity: "loading",
detail: "loading live data",
}
: module,
),
statusItems: dashboard.statusItems.map((item) =>
hydratingItemIds.has(`status:${item.id}`)
? {
...item,
severity: "loading",
value: "loading",
}
: item,
),
};
}
export function dashboardHydrationTiles(
document: DashboardDocument,
): DashboardTileReference[] {
const telemetry = document.telemetry
.filter((card) => card.datasource?.type === "external")
.map((card): DashboardTileReference => ({ kind: "telemetry", id: card.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,
})),
);
const modules = (document.modules || [])
.filter((module) =>
module.datasource?.type === "external" ||
module.id === "runtime-health-summary"
)
.map((module): DashboardTileReference => ({ kind: "module", id: module.id }));
const status = document.statusStrips.flatMap((strip) =>
strip.items
.map((item): DashboardTileReference => ({
kind: "status",
stripId: strip.id,
id: item.id,
})),
);
return [...status, ...telemetry, ...modules, ...services];
}
function dashboardTileKey(tile: DashboardTileReference): string {
if (tile.kind === "status") return `${tile.kind}:${tile.stripId}:${tile.id}`;
if (tile.kind === "service") return `${tile.kind}:${tile.groupId}:${tile.id}`;
return `${tile.kind}:${tile.id}`;
}
export function dashboardTileMatchKey(tile: DashboardTileReference): string {
return JSON.stringify(tile);
}
function dashboardTileModelId(tile: DashboardTileReference): string {
if (tile.kind === "status") return `${tile.stripId}:${tile.id}`;
return tile.id;
}
function dashboardTileUrl(tile: DashboardTileReference): string {
const parts = tile.kind === "status"
? ["api", "dashboard", "tile", tile.kind, tile.stripId, tile.id]
: tile.kind === "service"
? ["api", "dashboard", "tile", tile.kind, tile.groupId, tile.id]
: ["api", "dashboard", "tile", tile.kind, tile.id];
return `/${parts.map(encodeURIComponent).join("/")}`;
}
function applyDashboardTile(
document: DashboardDocument,
response: Extract<DashboardTileResponse, { state: "ready" }>,
): DashboardDocument {
if (response.tile.kind === "telemetry") {
return {
...document,
telemetry: document.telemetry.map((card) =>
card.id === response.tile.id ? response.item as TelemetryCard : card,
),
};
}
if (response.tile.kind === "service") {
const tile = response.tile;
return {
...document,
serviceGroups: document.serviceGroups.map((group) => ({
...group,
services: group.id === tile.groupId
? group.services.map((service) =>
service.id === tile.id ? response.item as ServiceEntry : service,
)
: group.services,
})),
};
}
if (response.tile.kind === "module") {
return {
...document,
modules: (document.modules || []).map((module) =>
module.id === response.tile.id ? response.item as DashboardModule : module,
),
};
}
const tile = response.tile;
return {
...document,
statusStrips: document.statusStrips.map((strip) =>
strip.id === tile.stripId
? {
...strip,
items: strip.items.map((item) =>
item.id === tile.id ? response.item as StatusItem : item,
),
}
: strip,
),
};
}

View file

@ -1,170 +0,0 @@
@import "tailwindcss";
@import "@dimensionlab/ui/styles.css";
@import "tw-animate-css";
@import "shadcn/tailwind.css";
@custom-variant dark (&:is(.dark *));
@theme inline {
--font-heading: var(--font-sans);
--font-sans: 'Geist Variable', sans-serif;
--color-sidebar-ring: var(--sidebar-ring);
--color-sidebar-border: var(--sidebar-border);
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
--color-sidebar-accent: var(--sidebar-accent);
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
--color-sidebar-primary: var(--sidebar-primary);
--color-sidebar-foreground: var(--sidebar-foreground);
--color-sidebar: var(--sidebar);
--color-chart-5: var(--chart-5);
--color-chart-4: var(--chart-4);
--color-chart-3: var(--chart-3);
--color-chart-2: var(--chart-2);
--color-chart-1: var(--chart-1);
--color-ring: var(--ring);
--color-input: var(--input);
--color-border: var(--border);
--color-destructive: var(--destructive);
--color-accent-foreground: var(--accent-foreground);
--color-accent: var(--accent);
--color-muted-foreground: var(--muted-foreground);
--color-muted: var(--muted);
--color-secondary-foreground: var(--secondary-foreground);
--color-secondary: var(--secondary);
--color-primary-foreground: var(--primary-foreground);
--color-primary: var(--primary);
--color-popover-foreground: var(--popover-foreground);
--color-popover: var(--popover);
--color-card-foreground: var(--card-foreground);
--color-card: var(--card);
--color-foreground: var(--foreground);
--color-background: var(--background);
--radius-sm: calc(var(--radius) * 0.6);
--radius-md: calc(var(--radius) * 0.8);
--radius-lg: var(--radius);
--radius-xl: calc(var(--radius) * 1.4);
--radius-2xl: calc(var(--radius) * 1.8);
--radius-3xl: calc(var(--radius) * 2.2);
--radius-4xl: calc(var(--radius) * 2.6);
}
:root,
.dark {
--background: var(--ui-color-canvas);
--foreground: var(--ui-color-text);
--card: var(--ui-color-surface);
--card-foreground: var(--ui-color-text);
--popover: var(--ui-color-surface-raised);
--popover-foreground: var(--ui-color-text);
--primary: var(--ui-color-accent);
--primary-foreground: var(--ui-color-canvas);
--secondary: var(--ui-color-surface-raised);
--secondary-foreground: var(--ui-color-text);
--muted: var(--ui-color-surface-raised);
--muted-foreground: var(--ui-color-muted);
--accent: var(--ui-color-accent);
--accent-foreground: var(--ui-color-canvas);
--destructive: var(--ui-color-danger);
--border: var(--ui-color-border);
--input: var(--ui-color-border-strong);
--ring: var(--ui-color-accent);
--chart-1: var(--ui-color-accent);
--chart-2: var(--ui-color-ok);
--chart-3: var(--ui-color-warning);
--chart-4: var(--ui-color-danger);
--chart-5: var(--ui-color-stale);
--radius: 0.5rem;
--sidebar: var(--ui-color-surface);
--sidebar-foreground: var(--ui-color-text);
--sidebar-primary: var(--ui-color-accent);
--sidebar-primary-foreground: var(--ui-color-canvas);
--sidebar-accent: var(--ui-color-surface-raised);
--sidebar-accent-foreground: var(--ui-color-text);
--sidebar-border: rgba(244, 244, 244, 0.16);
--sidebar-ring: var(--ui-color-accent);
}
@layer base {
* {
@apply border-border outline-ring/50;
}
body {
@apply bg-background text-foreground;
}
html {
@apply font-sans;
}
}
html {
background: var(--ui-color-canvas);
}
body {
min-width: 320px;
min-height: 100vh;
margin: 0;
background:
radial-gradient(circle at 50% 12%, transparent 0 42%, var(--ui-color-backdrop-vignette) 100%),
linear-gradient(var(--ui-color-grid-line) 1px, transparent 1px),
linear-gradient(90deg, var(--ui-color-grid-line) 1px, transparent 1px),
var(--ui-color-canvas);
background-size: auto, 40px 40px, 40px 40px, 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;
}
.state-shell {
display: grid;
min-height: 100vh;
align-content: center;
gap: var(--ui-space-3);
padding: var(--ui-space-4);
}
.state-shell__actions {
justify-self: center;
}
.state-shell ul {
display: grid;
max-width: 56rem;
gap: var(--ui-space-2);
margin: 0;
border: var(--ui-border);
background: var(--ui-color-surface-module);
color: var(--ui-color-muted);
font-size: 0.76rem;
list-style-position: inside;
padding: var(--ui-space-3);
}
@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;
}
}

View file

@ -1,633 +0,0 @@
import { describe, expect, test } from "vitest";
import {
attachDashboardRefreshLifecycle,
collectVisibleDashboardModelIds,
createDashboardPerformanceMarks,
createDashboardRefreshDelay,
dashboardPerformanceMarks,
createDashboardTileSnapshotStore,
createDashboardTileBackoff,
createDashboardRequestAborter,
runDashboardHydrationQueue,
runDashboardHydrationBatchQueue,
runViewportAwareDashboardHydrationQueue,
shouldPauseDashboardRefresh,
splitDashboardHydrationItemsByVisibility,
subscribeToDashboardTileEvents,
waitForDashboardHydrationIdle,
type DashboardIntersectionEntry,
} from "./dashboard-refresh";
describe("dashboard refresh lifecycle", () => {
test("pauses refreshes when the document is hidden or the browser is offline", () => {
expect(
shouldPauseDashboardRefresh({ visibilityState: "visible", online: true }),
).toBe(false);
expect(
shouldPauseDashboardRefresh({ visibilityState: "hidden", online: true }),
).toBe(true);
expect(
shouldPauseDashboardRefresh({ visibilityState: "visible", online: false }),
).toBe(true);
});
test("aborts shell and tile requests when a new shell run starts", () => {
const aborter = createDashboardRequestAborter();
const shellSignal = aborter.beginShellRun();
const tileSignal = aborter.beginTileRun();
const nextShellSignal = aborter.beginShellRun();
expect(shellSignal.aborted).toBe(true);
expect(tileSignal.aborted).toBe(true);
expect(nextShellSignal.aborted).toBe(false);
});
test("aborts active requests when refreshes are paused", () => {
const aborter = createDashboardRequestAborter();
const shellSignal = aborter.beginShellRun();
const tileSignal = aborter.beginTileRun();
aborter.abortActiveRequests();
expect(shellSignal.aborted).toBe(true);
expect(tileSignal.aborted).toBe(true);
});
test("pauses on hidden, offline, and pagehide events then resumes immediately when visible", () => {
const documentTarget = new EventTarget();
const windowTarget = new EventTarget();
const calls: string[] = [];
let paused = true;
const detach = attachDashboardRefreshLifecycle({
documentTarget,
windowTarget,
loadDashboard: () => {
calls.push("load");
},
pauseRefreshes: () => {
calls.push("pause");
},
refreshPaused: () => paused,
});
documentTarget.dispatchEvent(new Event("visibilitychange"));
windowTarget.dispatchEvent(new Event("offline"));
windowTarget.dispatchEvent(new Event("pagehide"));
paused = false;
documentTarget.dispatchEvent(new Event("visibilitychange"));
windowTarget.dispatchEvent(new Event("online"));
detach();
documentTarget.dispatchEvent(new Event("visibilitychange"));
windowTarget.dispatchEvent(new Event("online"));
expect(calls).toEqual(["pause", "pause", "pause", "load", "load"]);
});
test("limits tile hydration concurrency", async () => {
let active = 0;
let maxActive = 0;
const started: number[] = [];
const releases = new Map<number, () => void>();
const queue = runDashboardHydrationQueue({
concurrency: 2,
items: [1, 2, 3, 4],
signal: new AbortController().signal,
hydrate: async (item) => {
active += 1;
maxActive = Math.max(maxActive, active);
started.push(item);
await new Promise<void>((resolve) => releases.set(item, resolve));
active -= 1;
},
});
await waitFor(() => started.length === 2);
expect(started).toEqual([1, 2]);
expect(maxActive).toBe(2);
releases.get(1)?.();
await waitFor(() => started.length === 3);
expect(started).toEqual([1, 2, 3]);
expect(maxActive).toBe(2);
releases.get(2)?.();
releases.get(3)?.();
await waitFor(() => started.length === 4);
releases.get(4)?.();
await queue;
expect(maxActive).toBe(2);
});
test("hydrates queued work in fixed-size batches", async () => {
const batches: number[][] = [];
await runDashboardHydrationBatchQueue({
batchSize: 3,
hydrateBatch: (items) => {
batches.push(items);
},
items: [1, 2, 3, 4, 5, 6, 7],
signal: new AbortController().signal,
});
expect(batches).toEqual([
[1, 2, 3],
[4, 5, 6],
[7],
]);
});
test("hydrates visible items before deferred items and waits for idle", async () => {
const calls: string[] = [];
const idleReleases: Array<() => void> = [];
const hydration = runViewportAwareDashboardHydrationQueue({
collectVisibleModelIds: async () => new Set(["b"]),
concurrency: 1,
getModelId: (item) => item,
hydrate: async (item) => {
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");
await new Promise<void>((resolve) => idleReleases.push(resolve));
},
});
await waitFor(() => calls.includes("idle"));
expect(calls).toEqual(["b", "visible-settled", "idle"]);
idleReleases.shift()?.();
await hydration;
expect(calls).toEqual([
"b",
"visible-settled",
"idle",
"a",
"c",
"all-settled",
]);
});
test("splits visible hydration items while preserving document order", () => {
expect(splitDashboardHydrationItemsByVisibility({
getModelId: (item) => item.id,
items: [{ id: "status" }, { id: "telemetry" }, { id: "service" }],
visibleModelIds: new Set(["service", "status"]),
})).toEqual({
visible: [{ id: "status" }, { id: "service" }],
deferred: [{ id: "telemetry" }],
});
});
test("collects visible data-model-id elements with IntersectionObserver", async () => {
const elements = [
modelElement("status"),
modelElement("telemetry"),
modelElement("unrelated"),
];
let callback:
| ((entries: DashboardIntersectionEntry[]) => void)
| undefined;
let finishObservation: (() => void) | undefined;
let disconnected = false;
const observed: string[] = [];
const visible = collectVisibleDashboardModelIds({
createObserver: (observerCallback) => {
callback = observerCallback;
return {
disconnect() {
disconnected = true;
},
observe(element) {
observed.push(element.getAttribute("data-model-id") || "");
},
};
},
documentTarget: {
querySelectorAll: () => elements,
},
modelIds: ["status", "telemetry"],
setTimeout: (handler) => {
finishObservation = handler;
return 1 as unknown as ReturnType<typeof setTimeout>;
},
clearTimeout: () => undefined,
signal: new AbortController().signal,
});
callback?.([
{
isIntersecting: false,
intersectionRatio: 0,
target: elements[0],
},
{
isIntersecting: true,
target: elements[1],
},
]);
finishObservation?.();
expect(await visible).toEqual(new Set(["telemetry"]));
expect(observed).toEqual(["status", "telemetry"]);
expect(disconnected).toBe(true);
});
test("waits for requestIdleCallback when available", async () => {
let idleCallback: (() => void) | undefined;
let cancelledIdle: number | undefined;
const wait = waitForDashboardHydrationIdle({
cancelIdleCallback: (handle) => {
cancelledIdle = handle;
},
requestIdleCallback: (callback) => {
idleCallback = callback;
return 7;
},
signal: new AbortController().signal,
});
expect(cancelledIdle).toBeUndefined();
idleCallback?.();
await wait;
expect(cancelledIdle).toBe(7);
});
test("jitters refresh delays and slows repeated failures", () => {
const delay = createDashboardRefreshDelay({
jitterRatio: 0.1,
random: () => 1,
});
expect(delay.nextDelayMs(1_000)).toBe(1_100);
delay.recordFailure();
expect(delay.nextDelayMs(1_000)).toBe(2_200);
delay.recordFailure();
expect(delay.nextDelayMs(1_000)).toBe(4_400);
delay.recordSuccess();
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("subscribes to dashboard tile events", () => {
const received: unknown[] = [];
let listener: ((event: MessageEvent<string>) => 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<string>);
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();
backoff.recordFailure("telemetry:infra-ram", 1_000);
expect(backoff.canAttempt("telemetry:infra-ram", 15_999)).toBe(false);
expect(backoff.canAttempt("telemetry:infra-ram", 16_000)).toBe(true);
backoff.recordFailure("telemetry:infra-ram", 16_000);
expect(backoff.canAttempt("telemetry:infra-ram", 45_999)).toBe(false);
expect(backoff.canAttempt("telemetry:infra-ram", 46_000)).toBe(true);
backoff.recordFailure("telemetry:infra-ram", 46_000);
backoff.recordFailure("telemetry:infra-ram", 106_000);
expect(backoff.canAttempt("telemetry:infra-ram", 225_999)).toBe(false);
expect(backoff.canAttempt("telemetry:infra-ram", 226_000)).toBe(true);
backoff.recordSuccess("telemetry:infra-ram");
expect(backoff.canAttempt("telemetry:infra-ram", 107_000)).toBe(true);
});
test("stores only ready tile snapshots and restores them for matching revisions", () => {
const storage = createMemoryStorage();
let now = 1_000;
const store = createDashboardTileSnapshotStore(storage, {
now: () => now,
});
store.saveReadyTile({
currentRevisionId: "revision-a",
response: {
state: "ready",
tile: { kind: "telemetry", id: "infra-ram" },
item: {
id: "infra-ram",
label: "Infra RAM",
value: { kind: "percent", value: 42 },
detail: "live",
severity: "ok",
},
},
schemaVersion: "dashboard.v1",
});
store.saveTile({
currentRevisionId: "revision-a",
response: {
state: "not_found",
tile: { kind: "telemetry", id: "missing" },
message: "Missing",
},
schemaVersion: "dashboard.v1",
});
store.saveTile({
currentRevisionId: "revision-a",
response: {
state: "disabled",
tile: { kind: "module", id: "disabled-module" },
message: "Disabled",
},
schemaVersion: "dashboard.v1",
});
now = 61_000;
const restored = store.restore({
currentRevisionId: "revision-a",
schemaVersion: "dashboard.v1",
});
expect(restored).toEqual([
{
ageMs: 60_000,
response: {
state: "ready",
tile: { kind: "telemetry", id: "infra-ram" },
item: {
id: "infra-ram",
label: "Infra RAM",
value: { kind: "percent", value: 42 },
detail: "live - stale 60s",
severity: "stale",
},
},
},
]);
expect(store.restore({
currentRevisionId: "revision-b",
schemaVersion: "dashboard.v1",
})).toEqual([]);
});
test("ignores malformed stored tile snapshots", () => {
const storage = createMemoryStorage();
storage.setItem(
"dimensionlab.dashboard.tiles.v1",
JSON.stringify({
currentRevisionId: "revision-a",
schemaVersion: "dashboard.v1",
tiles: [
{},
{
item: {
id: "incomplete",
detail: "live",
},
savedAt: 1_000,
tile: { kind: "telemetry", id: "incomplete" },
},
{
item: {
id: "infra-ram",
label: "Infra RAM",
value: { kind: "percent", value: 42 },
detail: "live",
severity: "ok",
},
savedAt: 1_000,
tile: { kind: "telemetry", id: "infra-ram" },
},
{
item: { id: "broken-detail", detail: 42 },
savedAt: 1_000,
tile: { kind: "telemetry", id: "broken-detail" },
},
],
version: 1,
}),
);
const store = createDashboardTileSnapshotStore(storage, {
now: () => 16_000,
});
expect(store.restore({
currentRevisionId: "revision-a",
schemaVersion: "dashboard.v1",
})).toEqual([
{
ageMs: 15_000,
response: {
state: "ready",
tile: { kind: "telemetry", id: "infra-ram" },
item: {
id: "infra-ram",
label: "Infra RAM",
value: { kind: "percent", value: 42 },
detail: "live - stale 15s",
severity: "stale",
},
},
},
]);
});
test("ignores restored aggregate health snapshots", () => {
const storage = createMemoryStorage();
storage.setItem(
"dimensionlab.dashboard.tiles.v1",
JSON.stringify({
currentRevisionId: "revision-a",
schemaVersion: "dashboard.v1",
tiles: [
{
item: {
id: "system-status",
label: "System Status",
value: "20 services down",
severity: "danger",
},
savedAt: 1_000,
tile: { kind: "status", stripId: "footer-status", id: "system-status" },
},
{
item: {
id: "runtime-health-summary",
kind: "summary",
title: "Runtime Health",
value: "20 services down",
detail: "0 warnings - 8 services ok",
severity: "danger",
},
savedAt: 1_000,
tile: { kind: "module", id: "runtime-health-summary" },
},
{
item: {
id: "infra-ram",
label: "Infra RAM",
value: { kind: "percent", value: 42 },
detail: "live",
severity: "ok",
},
savedAt: 1_000,
tile: { kind: "telemetry", id: "infra-ram" },
},
],
version: 1,
}),
);
const store = createDashboardTileSnapshotStore(storage, {
now: () => 16_000,
});
expect(store.restore({
currentRevisionId: "revision-a",
schemaVersion: "dashboard.v1",
})).toEqual([
{
ageMs: 15_000,
response: {
state: "ready",
tile: { kind: "telemetry", id: "infra-ram" },
item: {
id: "infra-ram",
label: "Infra RAM",
value: { kind: "percent", value: 42 },
detail: "live - stale 15s",
severity: "stale",
},
},
},
]);
});
});
async function waitFor(predicate: () => boolean) {
for (let attempt = 0; attempt < 20; attempt += 1) {
if (predicate()) return;
await Promise.resolve();
}
throw new Error("condition was not met");
}
function createMemoryStorage(): Storage {
const values = new Map<string, string>();
return {
get length() {
return values.size;
},
clear() {
values.clear();
},
getItem(key) {
return values.get(key) ?? null;
},
key(index) {
return [...values.keys()][index] ?? null;
},
removeItem(key) {
values.delete(key);
},
setItem(key, value) {
values.set(key, value);
},
};
}
function modelElement(modelId: string) {
return {
getAttribute(name: string) {
return name === "data-model-id" ? modelId : null;
},
};
}

View file

@ -1,886 +0,0 @@
export interface DashboardRefreshPauseState {
visibilityState: DocumentVisibilityState;
online: boolean;
}
export type DashboardTileReference =
| { kind: "telemetry"; id: string }
| { kind: "service"; groupId: string; id: string }
| { kind: "module"; id: string }
| { kind: "status"; stripId: string; id: string };
type SnapshotMetricValue = Record<string, unknown>;
export type DashboardTileSnapshotItem = {
detail?: string;
id: string;
label?: string;
severity?: string;
value?: SnapshotMetricValue | string;
} & Record<string, unknown>;
export type DashboardTileSnapshotResponse =
| {
state: "ready";
tile: DashboardTileReference;
item: DashboardTileSnapshotItem;
}
| {
state: "not_found";
tile: DashboardTileReference;
message: string;
}
| {
state: "disabled";
tile: DashboardTileReference;
message: string;
};
export function shouldPauseDashboardRefresh(
state: DashboardRefreshPauseState,
): boolean {
return state.visibilityState !== "visible" || !state.online;
}
export function createDashboardRequestAborter() {
let shellController: AbortController | undefined;
let tileController: AbortController | undefined;
function abortController(controller: AbortController | undefined) {
if (controller && !controller.signal.aborted) {
controller.abort();
}
}
return {
beginShellRun(): AbortSignal {
abortController(shellController);
abortController(tileController);
shellController = new AbortController();
tileController = undefined;
return shellController.signal;
},
beginTileRun(): AbortSignal {
abortController(tileController);
tileController = new AbortController();
return tileController.signal;
},
abortActiveRequests(): void {
abortController(shellController);
abortController(tileController);
shellController = undefined;
tileController = undefined;
},
};
}
type DashboardRefreshEventTarget = Pick<
EventTarget,
"addEventListener" | "removeEventListener"
>;
export interface DashboardRefreshLifecycleOptions {
documentTarget: DashboardRefreshEventTarget;
windowTarget: DashboardRefreshEventTarget;
loadDashboard: () => void | Promise<void>;
pauseRefreshes: () => void;
refreshPaused: () => boolean;
}
export function attachDashboardRefreshLifecycle(
options: DashboardRefreshLifecycleOptions,
): () => void {
function handleRefreshLifecycleChange() {
if (options.refreshPaused()) {
options.pauseRefreshes();
return;
}
void options.loadDashboard();
}
function handlePageHide() {
options.pauseRefreshes();
}
options.documentTarget.addEventListener(
"visibilitychange",
handleRefreshLifecycleChange,
);
options.windowTarget.addEventListener("online", handleRefreshLifecycleChange);
options.windowTarget.addEventListener("offline", handleRefreshLifecycleChange);
options.windowTarget.addEventListener("pagehide", handlePageHide);
return () => {
options.documentTarget.removeEventListener(
"visibilitychange",
handleRefreshLifecycleChange,
);
options.windowTarget.removeEventListener("online", handleRefreshLifecycleChange);
options.windowTarget.removeEventListener("offline", handleRefreshLifecycleChange);
options.windowTarget.removeEventListener("pagehide", handlePageHide);
};
}
export interface DashboardHydrationQueueOptions<TItem> {
concurrency: number;
hydrate: (item: TItem) => Promise<void> | void;
items: TItem[];
signal: AbortSignal;
}
export async function runDashboardHydrationQueue<TItem>(
options: DashboardHydrationQueueOptions<TItem>,
): Promise<void> {
const concurrency = Math.max(1, Math.floor(options.concurrency));
let nextIndex = 0;
async function worker() {
while (!options.signal.aborted) {
const item = options.items[nextIndex];
nextIndex += 1;
if (item === undefined) return;
await options.hydrate(item);
}
}
const workerCount = Math.min(concurrency, options.items.length);
await Promise.all(
Array.from({ length: workerCount }, () => worker()),
);
}
export interface DashboardViewportHydrationQueueOptions<TItem> {
batchSize?: number;
collectVisibleModelIds: () => Promise<ReadonlySet<string>>;
concurrency: number;
getModelId: (item: TItem) => string;
hydrate: (item: TItem) => Promise<void> | void;
hydrateBatch?: (items: TItem[]) => Promise<void> | void;
items: TItem[];
onAllItemsSettled?: () => void;
onVisibleItemsSettled?: () => void;
signal: AbortSignal;
waitForIdle: () => Promise<void>;
}
export async function runViewportAwareDashboardHydrationQueue<TItem>(
options: DashboardViewportHydrationQueueOptions<TItem>,
): Promise<void> {
const visibleModelIds = await options.collectVisibleModelIds();
if (options.signal.aborted) return;
const { visible, deferred } = splitDashboardHydrationItemsByVisibility({
getModelId: options.getModelId,
items: options.items,
visibleModelIds,
});
await runDashboardHydrationItems(options, visible);
if (options.signal.aborted) return;
options.onVisibleItemsSettled?.();
if (!deferred.length) {
options.onAllItemsSettled?.();
return;
}
await options.waitForIdle();
if (options.signal.aborted) return;
await runDashboardHydrationItems(options, deferred);
options.onAllItemsSettled?.();
}
async function runDashboardHydrationItems<TItem>(
options: DashboardViewportHydrationQueueOptions<TItem>,
items: TItem[],
): Promise<void> {
if (!options.hydrateBatch) {
await runDashboardHydrationQueue({
concurrency: options.concurrency,
hydrate: options.hydrate,
items,
signal: options.signal,
});
return;
}
await runDashboardHydrationBatchQueue({
batchSize: options.batchSize ?? options.concurrency,
hydrateBatch: options.hydrateBatch,
items,
signal: options.signal,
});
}
export async function runDashboardHydrationBatchQueue<TItem>(options: {
batchSize: number;
hydrateBatch: (items: TItem[]) => Promise<void> | void;
items: TItem[];
signal: AbortSignal;
}): Promise<void> {
const batchSize = Math.max(1, Math.floor(options.batchSize));
for (let index = 0; index < options.items.length; index += batchSize) {
if (options.signal.aborted) return;
await options.hydrateBatch(options.items.slice(index, index + batchSize));
}
}
export function splitDashboardHydrationItemsByVisibility<TItem>(options: {
getModelId: (item: TItem) => string;
items: TItem[];
visibleModelIds: ReadonlySet<string>;
}): {
deferred: TItem[];
visible: TItem[];
} {
const visible: TItem[] = [];
const deferred: TItem[] = [];
for (const item of options.items) {
if (options.visibleModelIds.has(options.getModelId(item))) {
visible.push(item);
} else {
deferred.push(item);
}
}
return { visible, deferred };
}
export interface DashboardModelElement {
getAttribute(name: string): string | null;
}
export interface DashboardViewportElementSource {
querySelectorAll(selector: string): ArrayLike<DashboardModelElement>;
}
export interface DashboardIntersectionEntry {
intersectionRatio?: number;
isIntersecting: boolean;
target: DashboardModelElement;
}
export interface DashboardIntersectionObserver {
disconnect(): void;
observe(element: DashboardModelElement): void;
}
export type DashboardIntersectionObserverFactory = (
callback: (entries: DashboardIntersectionEntry[]) => void,
) => DashboardIntersectionObserver;
type DashboardTimerHandle = ReturnType<typeof setTimeout>;
export interface DashboardVisibleModelIdCollectorOptions {
clearTimeout?: (handle: DashboardTimerHandle) => void;
createObserver?: DashboardIntersectionObserverFactory;
documentTarget: DashboardViewportElementSource;
modelIds: Iterable<string>;
setTimeout?: (callback: () => void, timeoutMs: number) => DashboardTimerHandle;
signal: AbortSignal;
timeoutMs?: number;
}
export async function collectVisibleDashboardModelIds(
options: DashboardVisibleModelIdCollectorOptions,
): Promise<Set<string>> {
const targetIds = new Set(options.modelIds);
if (!targetIds.size || options.signal.aborted) return new Set();
const elements = Array.from(
options.documentTarget.querySelectorAll("[data-model-id]"),
).filter((element) => {
const modelId = element.getAttribute("data-model-id");
return modelId ? targetIds.has(modelId) : false;
});
if (!elements.length) return new Set();
if (!options.createObserver) return targetIds;
const createObserver = options.createObserver;
const setTimer =
options.setTimeout ||
((callback: () => void, timeoutMs: number) =>
globalThis.setTimeout(callback, timeoutMs));
const clearTimer =
options.clearTimeout ||
((handle: DashboardTimerHandle) => globalThis.clearTimeout(handle));
const timeoutMs = options.timeoutMs ?? 80;
return new Promise((resolve) => {
const visibleModelIds = new Set<string>();
let settled = false;
let timeoutHandle: DashboardTimerHandle | undefined;
let observer: DashboardIntersectionObserver | undefined;
function cleanup() {
if (timeoutHandle !== undefined) {
clearTimer(timeoutHandle);
}
observer?.disconnect();
options.signal.removeEventListener("abort", finish);
}
function finish() {
if (settled) return;
settled = true;
cleanup();
resolve(visibleModelIds);
}
observer = createObserver((entries) => {
for (const entry of entries) {
const modelId = entry.target.getAttribute("data-model-id");
if (
modelId &&
targetIds.has(modelId) &&
(entry.isIntersecting || (entry.intersectionRatio ?? 0) > 0)
) {
visibleModelIds.add(modelId);
}
}
});
for (const element of elements) {
observer.observe(element);
}
options.signal.addEventListener("abort", finish, { once: true });
timeoutHandle = setTimer(finish, timeoutMs);
});
}
export interface DashboardHydrationIdleOptions {
cancelIdleCallback?: (handle: number) => void;
clearTimeout?: (handle: DashboardTimerHandle) => void;
requestIdleCallback?: (
callback: () => void,
options?: { timeout?: number },
) => number;
setTimeout?: (callback: () => void, timeoutMs: number) => DashboardTimerHandle;
signal: AbortSignal;
timeoutMs?: number;
}
export async function waitForDashboardHydrationIdle(
options: DashboardHydrationIdleOptions,
): Promise<void> {
if (options.signal.aborted) return;
const setTimer =
options.setTimeout ||
((callback: () => void, timeoutMs: number) =>
globalThis.setTimeout(callback, timeoutMs));
const clearTimer =
options.clearTimeout ||
((handle: DashboardTimerHandle) => globalThis.clearTimeout(handle));
await new Promise<void>((resolve) => {
let settled = false;
let idleHandle: number | undefined;
let timeoutHandle: DashboardTimerHandle | undefined;
function cleanup() {
if (idleHandle !== undefined) {
options.cancelIdleCallback?.(idleHandle);
}
if (timeoutHandle !== undefined) {
clearTimer(timeoutHandle);
}
options.signal.removeEventListener("abort", finish);
}
function finish() {
if (settled) return;
settled = true;
cleanup();
resolve();
}
options.signal.addEventListener("abort", finish, { once: true });
if (options.requestIdleCallback) {
idleHandle = options.requestIdleCallback(finish, {
timeout: options.timeoutMs ?? 1_000,
});
} else {
timeoutHandle = setTimer(finish, 0);
}
});
}
export interface DashboardRefreshDelayOptions {
failureMultiplierLimit?: number;
jitterRatio?: number;
random?: () => number;
}
export function createDashboardRefreshDelay(
options: DashboardRefreshDelayOptions = {},
) {
const random = options.random || Math.random;
const jitterRatio = options.jitterRatio ?? 0.1;
const failureMultiplierLimit = options.failureMultiplierLimit ?? 8;
let consecutiveFailures = 0;
return {
nextDelayMs(baseDelayMs: number): number {
const failureMultiplier = consecutiveFailures
? Math.min(2 ** consecutiveFailures, failureMultiplierLimit)
: 1;
const jitterFactor = 1 + ((random() * 2) - 1) * jitterRatio;
return Math.max(0, Math.round(baseDelayMs * failureMultiplier * jitterFactor));
},
recordFailure(): void {
consecutiveFailures += 1;
},
recordSuccess(): void {
consecutiveFailures = 0;
},
};
}
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);
},
};
}
export interface DashboardTileEventSource {
addEventListener(
type: "dashboard-tile",
listener: (event: MessageEvent<string>) => 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() {
const failures = new Map<string, { attempts: number; nextAttemptAt: number }>();
return {
canAttempt(key: string, now = Date.now()): boolean {
const failure = failures.get(key);
return !failure || now >= failure.nextAttemptAt;
},
recordFailure(key: string, now = Date.now()): void {
const previousAttempts = failures.get(key)?.attempts || 0;
const attempts = previousAttempts + 1;
const delay =
dashboardTileBackoffDelaysMs[
Math.min(attempts - 1, dashboardTileBackoffDelaysMs.length - 1)
];
failures.set(key, {
attempts,
nextAttemptAt: now + delay,
});
},
recordSuccess(key: string): void {
failures.delete(key);
},
};
}
interface DashboardTileSnapshotRecord {
item: DashboardTileSnapshotItem;
savedAt: number;
tile: DashboardTileReference;
}
interface DashboardTileSnapshotPayload {
currentRevisionId: string;
schemaVersion: string;
tiles: DashboardTileSnapshotRecord[];
version: 1;
}
export interface DashboardTileSnapshotStoreContext {
currentRevisionId: string;
schemaVersion: string;
}
export interface DashboardTileSnapshotStoreOptions {
now?: () => number;
}
export interface RestoredDashboardTileSnapshot {
ageMs: number;
response: Extract<DashboardTileSnapshotResponse, { state: "ready" }>;
}
const dashboardTileSnapshotStorageKey = "dimensionlab.dashboard.tiles.v1";
export function createDashboardTileSnapshotStore(
storage: Storage | undefined,
options: DashboardTileSnapshotStoreOptions = {},
) {
const now = options.now || Date.now;
function read(): DashboardTileSnapshotPayload | null {
if (!storage) return null;
try {
const serialized = storage.getItem(dashboardTileSnapshotStorageKey);
if (!serialized) return null;
const payload = JSON.parse(serialized) as Partial<DashboardTileSnapshotPayload>;
if (
payload.version !== 1 ||
typeof payload.currentRevisionId !== "string" ||
typeof payload.schemaVersion !== "string" ||
!Array.isArray(payload.tiles)
) {
return null;
}
return {
currentRevisionId: payload.currentRevisionId,
schemaVersion: payload.schemaVersion,
tiles: payload.tiles
.filter(isDashboardTileSnapshotRecord)
.filter((record) => isPersistableDashboardTileSnapshot(record.tile)),
version: 1,
};
} catch {
return null;
}
}
function write(payload: DashboardTileSnapshotPayload): void {
if (!storage) return;
try {
storage.setItem(dashboardTileSnapshotStorageKey, JSON.stringify(payload));
} catch {
// Best-effort warm-start cache; quota and privacy failures are non-fatal.
}
}
function matchingPayload(
context: DashboardTileSnapshotStoreContext,
): DashboardTileSnapshotPayload {
const payload = read();
if (
payload &&
payload.currentRevisionId === context.currentRevisionId &&
payload.schemaVersion === context.schemaVersion
) {
return payload;
}
return {
currentRevisionId: context.currentRevisionId,
schemaVersion: context.schemaVersion,
tiles: [],
version: 1,
};
}
function saveReadyTile(
input: DashboardTileSnapshotStoreContext & {
response: Extract<DashboardTileSnapshotResponse, { state: "ready" }>;
},
): void {
if (!isPersistableDashboardTileSnapshot(input.response.tile)) return;
const payload = matchingPayload(input);
const key = dashboardTileSnapshotKey(input.response.tile);
const nextRecord: DashboardTileSnapshotRecord = {
item: input.response.item,
savedAt: now(),
tile: input.response.tile,
};
payload.tiles = [
nextRecord,
...payload.tiles.filter((record) =>
dashboardTileSnapshotKey(record.tile) !== key
),
];
write(payload);
}
return {
restore(context: DashboardTileSnapshotStoreContext): RestoredDashboardTileSnapshot[] {
const payload = read();
if (
!payload ||
payload.currentRevisionId !== context.currentRevisionId ||
payload.schemaVersion !== context.schemaVersion
) {
return [];
}
const restoredAt = now();
return payload.tiles.map((record) => ({
ageMs: Math.max(0, restoredAt - record.savedAt),
response: {
state: "ready",
tile: record.tile,
item: {
...record.item,
detail: staleDashboardTileDetail(record.item.detail, restoredAt - record.savedAt),
severity: "stale",
},
},
}));
},
saveReadyTile,
saveTile(input: DashboardTileSnapshotStoreContext & {
response: DashboardTileSnapshotResponse;
}): void {
if (input.response.state === "ready") {
saveReadyTile({
currentRevisionId: input.currentRevisionId,
response: input.response,
schemaVersion: input.schemaVersion,
});
}
},
};
}
function dashboardTileSnapshotKey(tile: DashboardTileReference): string {
return JSON.stringify(tile);
}
export function isPersistableDashboardTileSnapshot(
tile: DashboardTileReference,
): boolean {
if (tile.kind === "status" && tile.id === "system-status") return false;
if (tile.kind === "module" && tile.id === "runtime-health-summary") return false;
return true;
}
function isDashboardTileSnapshotRecord(
value: unknown,
): value is DashboardTileSnapshotRecord {
if (!isSnapshotRecord(value)) return false;
return (
typeof value.savedAt === "number" &&
Number.isFinite(value.savedAt) &&
isDashboardTileReference(value.tile) &&
isDashboardTileSnapshotItem(value.tile, value.item)
);
}
function isDashboardTileReference(
value: unknown,
): value is DashboardTileReference {
if (!isSnapshotRecord(value) || typeof value.kind !== "string") return false;
switch (value.kind) {
case "telemetry":
case "module":
return typeof value.id === "string";
case "service":
return typeof value.groupId === "string" && typeof value.id === "string";
case "status":
return typeof value.stripId === "string" && typeof value.id === "string";
default:
return false;
}
}
function isDashboardTileSnapshotItem(
tile: DashboardTileReference,
value: unknown,
): value is DashboardTileSnapshotItem {
if (!isSnapshotRecord(value) || value.id !== tile.id) return false;
switch (tile.kind) {
case "telemetry":
return (
typeof value.label === "string" &&
isMetricValue(value.value) &&
isSeverity(value.severity) &&
isOptionalString(value.detail) &&
isOptionalString(value.description) &&
isOptionalString(value.icon) &&
isOptionalNumberArray(value.sparkline)
);
case "service":
return (
typeof value.label === "string" &&
typeof value.description === "string" &&
isSeverity(value.severity) &&
isOptionalString(value.detail) &&
isOptionalString(value.icon) &&
isOptionalLink(value.link)
);
case "module":
return (
(value.kind === "summary" ||
value.kind === "weather" ||
value.kind === "custom") &&
isOptionalString(value.title) &&
isOptionalString(value.label) &&
isOptionalString(value.value) &&
isOptionalString(value.detail) &&
isOptionalString(value.icon) &&
(value.severity === undefined || isSeverity(value.severity))
);
case "status":
return (
typeof value.label === "string" &&
typeof value.value === "string" &&
isOptionalLink(value.link) &&
(value.severity === undefined || isSeverity(value.severity))
);
}
}
function isSnapshotRecord(value: unknown): value is Record<string, unknown> {
return typeof value === "object" && value !== null;
}
function isMetricValue(value: unknown): boolean {
if (!isSnapshotRecord(value) || typeof value.kind !== "string") return false;
if (value.kind === "text") {
return (
typeof value.value === "string" &&
isOptionalString(value.unit)
);
}
const numericKinds = ["bytes", "latency", "number", "percent", "temperature"];
if (!numericKinds.includes(value.kind)) return false;
if (typeof value.value !== "number" || !Number.isFinite(value.value)) return false;
if (value.kind === "percent" && (value.value < 0 || value.value > 100)) {
return false;
}
const precision = value.precision;
return (
isOptionalString(value.unit) &&
(precision === undefined ||
(typeof precision === "number" &&
Number.isInteger(precision) &&
precision >= 0 &&
precision <= 4))
);
}
function isSeverity(value: unknown): boolean {
return (
value === "neutral" ||
value === "ok" ||
value === "warning" ||
value === "danger" ||
value === "stale" ||
value === "unavailable"
);
}
function isOptionalString(value: unknown): boolean {
return value === undefined || typeof value === "string";
}
function isOptionalNumberArray(value: unknown): boolean {
return (
value === undefined ||
(Array.isArray(value) &&
value.every((item) => typeof item === "number" && Number.isFinite(item)))
);
}
function isOptionalLink(value: unknown): boolean {
return (
value === undefined ||
(isSnapshotRecord(value) &&
typeof value.href === "string" &&
isOptionalString(value.label) &&
(value.external === undefined || typeof value.external === "boolean"))
);
}
function staleDashboardTileDetail(
detail: string | undefined,
ageMs: number,
): string | undefined {
if (!detail) return detail;
const ageSeconds = Math.max(0, Math.floor(ageMs / 1_000));
return ageSeconds > 0 ? `${detail} - stale ${ageSeconds}s` : detail;
}

View file

@ -1,147 +0,0 @@
import { describe, expect, test } from "vitest";
import {
dimensionLabDashboardFixture,
} from "./dimensionlab";
import type {
DashboardDocument,
DatasourceReference,
ServiceEntry,
TelemetryCard,
} from "@dimensionlab/dashboard-model";
describe("Dimension Lab dashboard seed", () => {
test("defines the primary first-screen sections as model data", () => {
expect(dimensionLabDashboardFixture.metadata.title).toBe("System Overview");
expect(dimensionLabDashboardFixture.layout.telemetry).toHaveLength(16);
expect(dimensionLabDashboardFixture.layout.serviceGroups).toEqual([
"essentials",
"monitoring",
"ai-automation",
"systems",
"runtime-health",
]);
expect(dimensionLabDashboardFixture.layout.statusStrips).toEqual([
"footer-status",
]);
expect(dimensionLabDashboardFixture.layout.modules).toEqual([
"weather-amsterdam",
"runtime-health-summary",
]);
});
test("keeps icon, link, and datasource references in seed data", () => {
const iconIds = [
...dimensionLabDashboardFixture.telemetry.map((card) => card.icon),
...allServices(dimensionLabDashboardFixture).map((service) => service.icon),
...(dimensionLabDashboardFixture.modules || []).map((module) => module.icon),
].filter((icon): icon is string => Boolean(icon));
expect(
dimensionLabDashboardFixture.telemetry.every(
(card) => card.icon && card.datasource,
),
).toBe(true);
expect(
dimensionLabDashboardFixture.telemetry.some(
(card) => card.datasource?.type === "external" &&
card.datasource.adapter === "prometheus",
),
).toBe(true);
expect(
allServices(dimensionLabDashboardFixture).some(
(service) => service.datasource?.type === "external" &&
service.datasource.adapter === "http-status",
),
).toBe(true);
for (const service of allServices(dimensionLabDashboardFixture)) {
expect(service.icon, `${service.id} must declare an Iconify id`).toBeTruthy();
expect(verifiedSeedIconIds.has(service.icon || ""), `${service.id} icon must be verified`).toBe(true);
expect(service.datasource, `${service.id} must declare health source`).toBeTruthy();
if (service.link) {
expect(service.link.label, `${service.id} link needs an accessible label`).toBeTruthy();
expect(service.link.external).toBe(true);
}
}
for (const icon of iconIds) {
expect(verifiedSeedIconIds.has(icon), `${icon} must be verified`).toBe(true);
}
});
test("labels fallback values and unresolved adapters explicitly", () => {
const placeholders = collectDatasources(dimensionLabDashboardFixture).filter(
(datasource) => datasource.type === "placeholder",
);
const fallbackText = [
...dimensionLabDashboardFixture.telemetry.map((card) => card.detail || ""),
...allServices(dimensionLabDashboardFixture).map((service) => service.detail || ""),
...dimensionLabDashboardFixture.statusStrips.flatMap((strip) =>
strip.items.map((item) => item.value),
),
...(dimensionLabDashboardFixture.modules || []).map((module) => module.detail || ""),
].join("\n").toLowerCase();
expect(fallbackText).toContain("fallback");
for (const service of allServices(dimensionLabDashboardFixture)) {
expect(service.detail?.toLowerCase(), `${service.id} detail must label fallback state`).toContain("fallback");
}
for (const statusItem of dimensionLabDashboardFixture.statusStrips.flatMap((strip) => strip.items)) {
expect(statusItem.value.toLowerCase(), `${statusItem.id} value must label fallback state`).toContain("fallback");
}
expect(placeholders.length).toBeGreaterThan(0);
for (const datasource of placeholders) {
expect(datasource.reason.toLowerCase()).toMatch(/fallback|pending|unresolved/);
}
});
});
function allServices(document: DashboardDocument): ServiceEntry[] {
return document.serviceGroups.flatMap((group) => group.services);
}
function collectDatasources(document: DashboardDocument): DatasourceReference[] {
const telemetry = document.telemetry
.map((card: TelemetryCard) => card.datasource)
.filter((datasource): datasource is DatasourceReference => Boolean(datasource));
const services = allServices(document)
.map((service) => service.datasource)
.filter((datasource): datasource is DatasourceReference => Boolean(datasource));
const modules = (document.modules || [])
.map((module) => module.datasource)
.filter((datasource): datasource is DatasourceReference => Boolean(datasource));
return [...telemetry, ...services, ...modules];
}
const verifiedSeedIconIds = new Set([
"mdi:account-hard-hat-outline",
"mdi:backup-restore",
"mdi:brain",
"mdi:cpu-64-bit",
"mdi:docker",
"mdi:expansion-card",
"mdi:fan",
"mdi:harddisk",
"mdi:image-edit-outline",
"mdi:memory",
"mdi:pulse",
"mdi:robot-outline",
"mdi:router-network",
"mdi:thermometer",
"mdi:web",
"mdi:weather-sunny",
"simple-icons:adguard",
"simple-icons:adminer",
"simple-icons:amazonwebservices",
"simple-icons:cockpit",
"simple-icons:forgejo",
"simple-icons:grafana",
"simple-icons:n8n",
"simple-icons:ollama",
"simple-icons:postgresql",
"simple-icons:prometheus",
"simple-icons:uptimekuma",
"simple-icons:vaultwarden",
"simple-icons:wikidotjs",
]);

View file

@ -1,673 +0,0 @@
import {
DASHBOARD_SCHEMA_VERSION,
type DashboardDocument,
type DatasourceReference,
type MetricValue,
type ServiceEntry,
type ServiceGroup,
type Severity,
type TelemetryCard,
} from "@dimensionlab/dashboard-model";
type NumericValueKind = "percent" | "bytes" | "temperature" | "latency" | "number";
interface MetricSeed {
id: string;
label: string;
icon: string;
kind: NumericValueKind;
value: number;
detail: string;
severity: Severity;
datasource: DatasourceReference;
thresholds?: TelemetryCard["thresholds"];
sparkline?: number[];
precision?: number;
}
interface ServiceSeed {
id: string;
label: string;
description: string;
icon: string;
datasource: DatasourceReference;
href?: string;
severity?: Severity;
detail?: string;
}
const REAL_FILESYSTEM_FILTER =
'fstype!~"tmpfs|overlay|squashfs|nsfs|tracefs|autofs|proc|sysfs|cgroup2|devtmpfs|securityfs|debugfs|pstore|bpf|configfs|selinuxfs|mqueue|hugetlbfs|fusectl|ramfs"';
const USER_MOUNT_FILTER =
'mountpoint=~"^/home($|/)|^/srv($|/)|^/data($|/)|^/mnt($|/)|^/media($|/)",mountpoint!~"^/(boot|boot/efi|efi|var|usr|opt|run|dev|proc|sys)($|/)"';
const SYSTEM_MOUNT_FILTER =
'mountpoint=~"^/$|^/boot($|/)|^/boot/efi$|^/var($|/)|^/usr($|/)|^/opt($|/)",mountpoint!~"^/home($|/)|^/srv($|/)|^/data($|/)|^/mnt($|/)|^/media($|/)"';
const diskUsedQuery = (mountFilter: string) => {
const selector = `job="node",${REAL_FILESYSTEM_FILTER},${mountFilter}`;
return `topk(1, max by (host, mountpoint) (100 * (1 - node_filesystem_avail_bytes{${selector}} / node_filesystem_size_bytes{${selector}})))`;
};
const ramQuery = (host: string) =>
`100 * (1 - node_memory_MemAvailable_bytes{job="node",host="${host}"} / node_memory_MemTotal_bytes{job="node",host="${host}"})`;
const gpuQuery = (name: string, metric: string) =>
`${metric}{job="node",name=~".*${name}.*"}`;
const gpuVramQuery = (name: string) =>
`100 * nvidia_gpu_memory_used_bytes{job="node",name=~".*${name}.*"} / nvidia_gpu_memory_total_bytes{job="node",name=~".*${name}.*"}`;
const hostCpuQuery =
'topk(1, 100 * (1 - avg by (host) (rate(node_cpu_seconds_total{job="node",mode="idle"}[5m]))))';
const topCpuQuery =
'topk(1, 100 * rate(podman_container_cpu_seconds_total{job=~"podman-.*"}[5m]) * on(host,id) group_left(name) podman_container_info{job=~"podman-.*"})';
const topRamQuery =
'topk(1, podman_container_mem_usage_bytes{job=~"podman-.*"} * on(host,id) group_left(name) podman_container_info{job=~"podman-.*"})';
export const dimensionLabDashboardFixture: DashboardDocument = {
schemaVersion: DASHBOARD_SCHEMA_VERSION,
metadata: {
title: "System Overview",
subtitle: "Capacity, noise & top consumers",
description:
"Initial Dimension Lab dashboard seed data. Runtime values are fallback values until datasource adapters are enabled.",
timezone: "Europe/Amsterdam",
refreshIntervalSeconds: 15,
},
layout: {
density: "dense",
telemetry: [
"infra-ram",
"gpu-host-ram",
"network-ram",
"user-disk-peak",
"system-disk-peak",
"peak-cpu-busy",
"top-cpu-container",
"top-ram-container",
"gpu-3060-load",
"gpu-3060-vram",
"gpu-3060-temp",
"gpu-3060-fan",
"gpu-3090-load",
"gpu-3090-vram",
"gpu-3090-temp",
"gpu-3090-fan",
],
serviceGroups: ["essentials", "monitoring", "ai-automation", "systems", "runtime-health"],
statusStrips: ["footer-status"],
modules: ["weather-amsterdam", "runtime-health-summary"],
},
telemetry: [
metric({
id: "infra-ram",
label: "Infra RAM",
icon: "mdi:memory",
kind: "percent",
value: 19,
detail: "fallback - linux-infra memory used",
severity: "ok",
thresholds: percentThresholds(),
datasource: prometheus(
ramQuery("linux-infra"),
),
sparkline: [18, 18, 19, 19, 18, 19],
}),
metric({
id: "gpu-host-ram",
label: "GPU Host RAM",
icon: "mdi:memory",
kind: "percent",
value: 17,
detail: "fallback - GPU host memory used",
severity: "ok",
thresholds: percentThresholds(),
datasource: prometheus(
ramQuery("linux"),
),
sparkline: [16, 16, 17, 17, 17, 17],
}),
metric({
id: "network-ram",
label: "Network RAM",
icon: "mdi:router-network",
kind: "percent",
value: 5,
detail: "fallback - network core memory used",
severity: "ok",
thresholds: percentThresholds(),
datasource: prometheus(
ramQuery("network-core"),
),
sparkline: [5, 5, 5, 6, 5, 5],
}),
metric({
id: "user-disk-peak",
label: "User Disk Peak",
icon: "mdi:harddisk",
kind: "percent",
value: 29,
detail: "fallback - /home peak usage",
severity: "ok",
thresholds: percentThresholds(80, 92),
datasource: prometheus(
diskUsedQuery(USER_MOUNT_FILTER),
),
sparkline: [27, 27, 28, 29, 29, 29],
}),
metric({
id: "system-disk-peak",
label: "System Disk Peak",
icon: "mdi:harddisk",
kind: "percent",
value: 49,
detail: "fallback - /boot peak usage",
severity: "ok",
thresholds: percentThresholds(80, 92),
datasource: prometheus(
diskUsedQuery(SYSTEM_MOUNT_FILTER),
),
sparkline: [48, 48, 49, 49, 49, 49],
}),
metric({
id: "peak-cpu-busy",
label: "Peak CPU Busy",
icon: "mdi:cpu-64-bit",
kind: "percent",
value: 3,
detail: "fallback - linux-infra 5m CPU busy",
severity: "ok",
thresholds: percentThresholds(75, 90),
datasource: prometheus(
hostCpuQuery,
),
sparkline: [2, 3, 3, 4, 3, 3],
}),
metric({
id: "top-cpu-container",
label: "Top CPU Container",
icon: "mdi:docker",
kind: "percent",
value: 7.9,
precision: 1,
detail: "fallback - top container CPU pending label mapping",
severity: "ok",
thresholds: percentThresholds(70, 90),
datasource: prometheus(topCpuQuery),
sparkline: [6.1, 6.4, 7.0, 7.5, 7.2, 7.9],
}),
metric({
id: "top-ram-container",
label: "Top RAM Container",
icon: "mdi:docker",
kind: "bytes",
value: Math.round(5.2 * 1024 ** 3),
precision: 1,
detail: "fallback - top container RAM pending label mapping",
severity: "danger",
thresholds: { warning: 3 * 1024 ** 3, danger: 5 * 1024 ** 3 },
datasource: prometheus(topRamQuery),
sparkline: [
3.1 * 1024 ** 3,
3.5 * 1024 ** 3,
4.1 * 1024 ** 3,
4.8 * 1024 ** 3,
5.0 * 1024 ** 3,
5.2 * 1024 ** 3,
],
}),
metric({
id: "gpu-3060-load",
label: "3060 GPU Load",
icon: "mdi:expansion-card",
kind: "percent",
value: 0,
detail: "fallback - RTX 3060 GPU utilization",
severity: "ok",
thresholds: percentThresholds(85, 95),
datasource: prometheus(gpuQuery("3060", "nvidia_gpu_utilization_percent")),
sparkline: [0, 0, 0, 0, 0, 0],
}),
metric({
id: "gpu-3060-vram",
label: "3060 VRAM",
icon: "mdi:memory",
kind: "percent",
value: 0,
detail: "fallback - RTX 3060 VRAM used",
severity: "ok",
thresholds: percentThresholds(80, 92),
datasource: prometheus(
gpuVramQuery("3060"),
),
sparkline: [0, 0, 0, 0, 0, 0],
}),
metric({
id: "gpu-3060-temp",
label: "3060 Temp",
icon: "mdi:thermometer",
kind: "temperature",
value: 54,
detail: "fallback - RTX 3060 temperature",
severity: "ok",
thresholds: { warning: 75, danger: 85 },
datasource: prometheus(gpuQuery("3060", "nvidia_gpu_temperature_celsius")),
sparkline: [52, 53, 54, 54, 53, 54],
}),
metric({
id: "gpu-3060-fan",
label: "3060 Fan Spin",
icon: "mdi:fan",
kind: "percent",
value: 0,
detail: "fallback - RTX 3060 fan duty",
severity: "ok",
thresholds: percentThresholds(80, 95),
datasource: prometheus(gpuQuery("3060", "nvidia_gpu_fan_speed_percent")),
sparkline: [0, 0, 0, 0, 0, 0],
}),
metric({
id: "gpu-3090-load",
label: "3090 GPU Load",
icon: "mdi:expansion-card",
kind: "percent",
value: 0,
detail: "fallback - RTX 3090 GPU utilization",
severity: "ok",
thresholds: percentThresholds(85, 95),
datasource: prometheus(gpuQuery("3090", "nvidia_gpu_utilization_percent")),
sparkline: [0, 0, 0, 0, 0, 0],
}),
metric({
id: "gpu-3090-vram",
label: "3090 VRAM",
icon: "mdi:memory",
kind: "percent",
value: 74,
detail: "fallback - RTX 3090 VRAM used",
severity: "warning",
thresholds: percentThresholds(70, 90),
datasource: prometheus(
gpuVramQuery("3090"),
),
sparkline: [68, 70, 72, 74, 73, 74],
}),
metric({
id: "gpu-3090-temp",
label: "3090 Temp",
icon: "mdi:thermometer",
kind: "temperature",
value: 50,
detail: "fallback - RTX 3090 temperature",
severity: "ok",
thresholds: { warning: 75, danger: 85 },
datasource: prometheus(gpuQuery("3090", "nvidia_gpu_temperature_celsius")),
sparkline: [49, 50, 50, 51, 50, 50],
}),
metric({
id: "gpu-3090-fan",
label: "3090 Fan Spin",
icon: "mdi:fan",
kind: "percent",
value: 0,
detail: "fallback - RTX 3090 fan duty",
severity: "ok",
thresholds: percentThresholds(80, 95),
datasource: prometheus(gpuQuery("3090", "nvidia_gpu_fan_speed_percent")),
sparkline: [0, 0, 0, 0, 0, 0],
}),
],
serviceGroups: [
group("essentials", "Essentials", [
service({
id: "vaultwarden",
label: "Vaultwarden",
description: "Password manager",
icon: "simple-icons:vaultwarden",
href: "https://vault.dimensionlab.net",
datasource: uptimeMonitor(1),
}),
service({
id: "forgejo",
label: "Forgejo",
description: "Git repositories",
icon: "simple-icons:forgejo",
href: "https://git.dimensionlab.net",
datasource: uptimeMonitor(2),
}),
service({
id: "wiki",
label: "Wiki",
description: "Internal documentation",
icon: "simple-icons:wikidotjs",
href: "https://wiki.dimensionlab.net",
datasource: uptimeMonitor(3),
}),
service({
id: "aws-start",
label: "AWS Start",
description: "AWS access portal",
icon: "simple-icons:amazonwebservices",
href: "https://dimensionlab.awsapps.com/start",
datasource: uptimeMonitor(21),
}),
service({
id: "adguard-primary",
label: "AdGuard Primary",
description: "DNS filtering and DHCP",
icon: "simple-icons:adguard",
href: "https://control.dimensionlab.net",
datasource: uptimeMonitor(14),
}),
service({
id: "adguard-secondary",
label: "AdGuard Secondary",
description: "Fallback DNS",
icon: "simple-icons:adguard",
href: "https://control-secondary.dimensionlab.net",
datasource: uptimeMonitor(18),
}),
]),
group("monitoring", "Monitoring", [
service({
id: "grafana",
label: "Grafana",
description: "Capacity and noise dashboard",
icon: "simple-icons:grafana",
href: "https://grafana.dimensionlab.net",
datasource: uptimeMonitor(11),
}),
service({
id: "uptime-kuma",
label: "Uptime Kuma",
description: "Service uptime checks",
icon: "simple-icons:uptimekuma",
href: "https://uptime.dimensionlab.net",
datasource: uptimeMonitor(10),
}),
service({
id: "prometheus",
label: "Prometheus",
description: "Metrics database",
icon: "simple-icons:prometheus",
href: "https://prometheus.dimensionlab.net",
datasource: uptimeMonitor(12),
}),
service({
id: "backrest",
label: "Backrest",
description: "Restic backup manager",
icon: "mdi:backup-restore",
href: "https://backups.dimensionlab.net",
datasource: uptimeMonitor(13),
}),
]),
group("ai-automation", "AI & Automation", [
service({
id: "n8n",
label: "n8n",
description: "Workflow automation",
icon: "simple-icons:n8n",
href: "https://workflows.dimensionlab.net",
datasource: uptimeMonitor(4),
}),
service({
id: "open-webui",
label: "Open WebUI",
description: "Chat and model interface",
icon: "mdi:web",
href: "https://webui.dimensionlab.net",
datasource: uptimeMonitor(5),
}),
service({
id: "comfyui",
label: "ComfyUI",
description: "Image generation workflows",
icon: "mdi:image-edit-outline",
href: "https://comfy.dimensionlab.net",
datasource: uptimeMonitor(6),
}),
service({
id: "models",
label: "Models",
description: "Local model management",
icon: "mdi:brain",
href: "https://models.dimensionlab.net",
datasource: uptimeMonitor(7),
}),
]),
group("systems", "Systems", [
service({
id: "adminer",
label: "Adminer",
description: "PostgreSQL database browser",
icon: "simple-icons:adminer",
href: "https://db.dimensionlab.net",
datasource: uptimeMonitor(17),
}),
service({
id: "assistant",
label: "Assistant",
description: "Personal AI agent gateway",
icon: "mdi:robot-outline",
href: "https://assistant.dimensionlab.net",
datasource: uptimeMonitor(8),
}),
service({
id: "suna",
label: "Suna",
description: "AI command center",
icon: "mdi:account-hard-hat-outline",
href: "https://suna.dimensionlab.net",
datasource: uptimeMonitor(9),
}),
service({
id: "cockpit-infra",
label: "Cockpit Infra",
description: "linux-infra server console",
icon: "simple-icons:cockpit",
href: "https://infra-cockpit.dimensionlab.net",
datasource: uptimeMonitor(15),
}),
service({
id: "cockpit-gpu",
label: "Cockpit GPU",
description: "Linux GPU server console",
icon: "simple-icons:cockpit",
href: "https://linux-cockpit.dimensionlab.net",
datasource: uptimeMonitor(16),
}),
service({
id: "cockpit-network-core",
label: "Cockpit Network Core",
description: "i3 NUC DNS/DHCP console",
icon: "simple-icons:cockpit",
href: "https://network-core.dimensionlab.net",
datasource: uptimeMonitor(19),
}),
]),
group("runtime-health", "Runtime Health", [
service({
id: "forgejo-ssh-relay",
label: "Forgejo SSH Relay",
description: "Public Git SSH relay",
icon: "simple-icons:forgejo",
href: "https://uptime.dimensionlab.net/status/dimensionlab",
datasource: uptimeMonitor(28),
detail: "fallback - uptime monitor pending",
}),
service({
id: "postgresql",
label: "PostgreSQL",
description: "Shared application database",
icon: "simple-icons:postgresql",
datasource: uptimeMonitor(22),
detail: "fallback - postgres exporter pending",
}),
service({
id: "ollama-api",
label: "Ollama API",
description: "Local model API",
icon: "simple-icons:ollama",
datasource: uptimeMonitor(23),
detail: "fallback - internal health check pending",
}),
service({
id: "node-exporter",
label: "Node Exporter",
description: "Host metrics exporter",
icon: "simple-icons:prometheus",
datasource: uptimeMonitor(24),
detail: "fallback - exporter health from Uptime Kuma",
}),
service({
id: "podman-user-exporter",
label: "Podman User Exporter",
description: "Rootless container metrics",
icon: "simple-icons:prometheus",
datasource: uptimeMonitor(25),
detail: "fallback - exporter health from Uptime Kuma",
}),
service({
id: "podman-system-exporter",
label: "Podman System Exporter",
description: "System container metrics",
icon: "simple-icons:prometheus",
datasource: uptimeMonitor(26),
detail: "fallback - exporter health from Uptime Kuma",
}),
service({
id: "network-core-node-exporter",
label: "Network Core Node Exporter",
description: "i3 DNS/DHCP metrics",
icon: "simple-icons:prometheus",
datasource: uptimeMonitor(27),
detail: "fallback - exporter health from Prometheus",
}),
], "grid"),
],
statusStrips: [
{
id: "footer-status",
items: [
{ id: "system-status", label: "System Status", value: "Fallback operational", severity: "ok" },
{ id: "last-sync", label: "Last Sync", value: "fallback: 2 minutes ago", severity: "stale" },
{ id: "uptime", label: "Uptime", value: "fallback: 28d 14h 32m", severity: "ok" },
{ id: "load-avg", label: "Load Avg", value: "fallback: 0.47 0.53 0.59", severity: "neutral" },
{ id: "auto-refresh", label: "Auto Refresh", value: "fallback: 15s", severity: "neutral" },
],
},
],
modules: [
{
id: "weather-amsterdam",
kind: "weather",
title: "Amsterdam",
value: "28.3 C",
detail: "fallback - weather adapter pending",
icon: "mdi:weather-sunny",
severity: "ok",
datasource: {
type: "external",
adapter: "weather",
reference: "open-meteo:latitude=52.3676&longitude=4.9041",
},
},
{
id: "runtime-health-summary",
kind: "summary",
title: "Runtime Health",
value: "fallback",
detail: "fallback - health adapters pending",
icon: "mdi:pulse",
severity: "stale",
datasource: placeholder("summary pending live health aggregation"),
},
],
};
function metric(seed: MetricSeed): TelemetryCard {
return {
id: seed.id,
label: seed.label,
icon: seed.icon,
value: metricValue(seed.kind, seed.value, seed.precision),
detail: seed.detail,
severity: seed.severity,
thresholds: seed.thresholds,
datasource: seed.datasource,
sparkline: seed.sparkline,
};
}
function metricValue(
kind: NumericValueKind,
value: number,
precision?: number,
): MetricValue {
return precision === undefined ? { kind, value } : { kind, value, precision };
}
function service(seed: ServiceSeed): ServiceEntry {
const entry: ServiceEntry = {
id: seed.id,
label: seed.label,
description: seed.description,
icon: seed.icon,
severity: seed.severity || "ok",
detail: seed.detail || "fallback - health check pending",
datasource: seed.datasource,
};
if (!seed.href) return entry;
return {
...entry,
link: {
href: seed.href,
label: `Open ${seed.label}`,
external: true,
},
};
}
function group(
id: string,
title: string,
services: ServiceEntry[],
layout: ServiceGroup["layout"] = "list",
): ServiceGroup {
return {
id,
layout,
services,
title,
};
}
function percentThresholds(warning = 80, danger = 92) {
return { warning, danger };
}
function prometheus(reference: string): DatasourceReference {
return {
type: "external",
adapter: "prometheus",
reference,
};
}
function httpStatus(url: string): DatasourceReference {
return {
type: "external",
adapter: "http-status",
reference: `GET ${url}`,
};
}
function placeholder(reason: string): DatasourceReference {
return {
type: "placeholder",
reason,
};
}
function uptimeMonitor(id: number): DatasourceReference {
return httpStatus(`https://uptime.dimensionlab.net/_homepage-badge/${id}`);
}

View file

@ -1,80 +0,0 @@
import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
import { join } from "node:path";
import { describe, expect, test } from "vitest";
const appRoot = process.cwd().endsWith(`${join("apps", "web")}`)
? process.cwd()
: join(process.cwd(), "apps", "web");
const repoRoot = existsSync(join(process.cwd(), "turbo.json"))
? process.cwd()
: join(appRoot, "..", "..");
const presentationRoots = [
join(repoRoot, "packages", "ui", "src"),
join(appRoot, "src", "App.tsx"),
join(appRoot, "src", "app.css"),
join(appRoot, "src", "lib", "ui-adapter"),
];
const forbiddenTerms = [
"dimensionlab",
"dimension lab",
"vaultwarden",
"forgejo",
"grafana",
"uptime kuma",
"prometheus",
"backrest",
"open webui",
"comfyui",
"adminer",
"cockpit",
"ollama",
"dimensionlab.net",
];
describe("presentation content boundary", () => {
test("keeps environment-specific content out of route and UI implementation", () => {
const source = withoutInternalPackageScope(
presentationRoots.map(readPresentationSource).join("\n").toLowerCase(),
);
expect(forbiddenTerms.filter((term) => source.includes(term))).toEqual([]);
});
test("does not keep legacy presentation component files in the React runtime", () => {
const legacyExtension = [".sve", "lte"].join("");
expect(findFiles(join(appRoot, "src"), legacyExtension)).toEqual([]);
});
});
function readPresentationSource(path: string): string {
if (!existsSync(path)) return "";
const stats = statSync(path);
if (stats.isFile()) {
if (path.endsWith(".test.ts")) return "";
if (path.includes(`${join("packages", "ui", "src", "stories")}${"/"}`)) {
return "";
}
if (!/\.(tsx|ts|js|mjs|css|json)$/.test(path)) return "";
return readFileSync(path, "utf8");
}
return readdirSync(path)
.map((entry) => readPresentationSource(join(path, entry)))
.join("\n");
}
function findFiles(path: string, extension: string): string[] {
const stats = statSync(path);
if (stats.isFile()) return path.endsWith(extension) ? [path] : [];
return readdirSync(path).flatMap((entry) => findFiles(join(path, entry), extension));
}
function withoutInternalPackageScope(source: string): string {
return source
.replaceAll("@dimensionlab/dashboard-model", "@internal/dashboard-model")
.replaceAll("@dimensionlab/ui", "@internal/ui");
}

View file

@ -1,595 +0,0 @@
import { existsSync, rmSync } from "node:fs";
import { mkdtemp } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { afterEach, describe, expect, test } from "vitest";
import type { DashboardDocument } from "@dimensionlab/dashboard-model";
import { genericDashboardFixture } from "@dimensionlab/dashboard-model/fixtures";
import { createDashboardStore, type DashboardStore } from "$lib/server/db/dashboard-store";
import {
AgentConfigAuthorizationError,
authorizeAgentConfigRequest,
handleAgentDashboardRequest,
previewDashboardChanges,
publishDashboardChanges,
rollbackDashboardRevision,
type AgentDashboardOperation,
type JsonPatchOperation,
} from ".";
const stores: DashboardStore[] = [];
const tempRoots: string[] = [];
afterEach(() => {
stores.splice(0).forEach((store) => store.close());
tempRoots.splice(0).forEach((path) => rmSync(path, { force: true, recursive: true }));
});
describe("agent dashboard configuration API", () => {
test("previews typed dashboard operations with an RFC 6902-compatible patch", () => {
const operations = exampleOperations();
const result = previewDashboardChanges(genericDashboardFixture, operations);
expect(result.ok).toBe(true);
if (!result.ok) throw new Error("expected preview success");
expect(result.document.serviceGroups[0]?.id).toBe("edge");
expect(result.document.serviceGroups[0]?.services[0]).toMatchObject({
id: "edge-router",
datasource: {
type: "external",
adapter: "http-status",
reference: "GET https://edge.example.test/api/status",
},
});
expect(result.document.layout.telemetry).toEqual([
"service-uptime",
"edge-latency",
"queue-depth",
]);
expect(genericDashboardFixture.serviceGroups.map((group) => group.id)).toEqual([
"core-services",
]);
expect(result.patch.length).toBeGreaterThan(0);
expect(result.patch.every((operation) => operation.path.startsWith("/"))).toBe(true);
expect(result.patch.map((operation) => operation.op)).toContain("add");
});
test("returns structured repairable errors for invalid operations", () => {
const result = previewDashboardChanges(genericDashboardFixture, [
{
type: "add_service",
groupId: "missing-group",
service: exampleService(),
},
]);
expect(result.ok).toBe(false);
if (result.ok) throw new Error("expected preview failure");
expect(result.errors).toEqual([
expect.objectContaining({
code: "group_not_found",
operationIndex: 0,
path: "/serviceGroups",
}),
]);
});
test("rejects unsupported target-specific mutations", () => {
const datasourceResult = previewDashboardChanges(genericDashboardFixture, [
{
type: "connect_datasource",
target: { kind: "statusItem", stripId: "runtime", id: "status" },
datasource: {
type: "external",
adapter: "http-status",
reference: "GET https://status.example.test/api",
},
},
]);
expect(datasourceResult.ok).toBe(false);
if (datasourceResult.ok) throw new Error("expected datasource failure");
expect(datasourceResult.errors[0]).toMatchObject({
code: "unsupported_target",
operationIndex: 0,
path: "/statusStrips",
});
const thresholdResult = previewDashboardChanges(genericDashboardFixture, [
{
type: "set_status_rule",
target: { kind: "service", id: "identity" },
thresholds: { warning: 1 },
},
]);
expect(thresholdResult.ok).toBe(false);
if (thresholdResult.ok) throw new Error("expected threshold failure");
expect(thresholdResult.errors[0]).toMatchObject({
code: "unsupported_target",
operationIndex: 0,
path: "/serviceGroups",
});
});
test("removes status strip items through the shared remove operation", () => {
const result = previewDashboardChanges(genericDashboardFixture, [
{
type: "remove_item",
target: { kind: "statusItem", stripId: "runtime", id: "sync" },
},
]);
expect(result.ok).toBe(true);
if (!result.ok) throw new Error("expected remove success");
expect(result.document.statusStrips[0]?.items.map((item) => item.id)).toEqual([
"status",
]);
expect(result.patch.map((operation) => operation.op)).toContain("remove");
});
test("returns RFC 6902-applicable patches for multiple array removals", () => {
const result = previewDashboardChanges(genericDashboardFixture, [
{
type: "remove_item",
target: { kind: "statusItem", stripId: "runtime", id: "status" },
},
{
type: "remove_item",
target: { kind: "statusItem", stripId: "runtime", id: "sync" },
},
]);
expect(result.ok).toBe(true);
if (!result.ok) throw new Error("expected remove success");
expect(
result.patch
.filter((operation) => operation.op === "remove")
.map((operation) => operation.path),
).toEqual(["/statusStrips/0/items/1", "/statusStrips/0/items/0"]);
expect(applyJsonPatch(genericDashboardFixture, result.patch)).toMatchObject({
statusStrips: [{ items: [] }],
});
});
test("rejects ambiguous service and status targets", () => {
const duplicateDocument = documentWithDuplicateNestedIds();
const connect = previewDashboardChanges(duplicateDocument, [
{
type: "connect_datasource",
target: { kind: "service", id: "identity" },
datasource: {
type: "external",
adapter: "http-status",
reference: "GET https://identity.example.test/health",
},
},
]);
expect(connect.ok).toBe(false);
if (connect.ok) throw new Error("expected ambiguous service failure");
expect(connect.errors[0]).toMatchObject({
code: "ambiguous_target",
operationIndex: 0,
path: "/serviceGroups",
});
const remove = previewDashboardChanges(duplicateDocument, [
{
type: "remove_item",
target: { kind: "service", id: "identity" },
},
]);
expect(remove.ok).toBe(false);
if (remove.ok) throw new Error("expected ambiguous removal failure");
expect(remove.errors[0]).toMatchObject({
code: "ambiguous_target",
operationIndex: 0,
path: "/serviceGroups",
});
const status = previewDashboardChanges(duplicateDocument, [
{
type: "set_status_rule",
target: { kind: "statusItem", id: "status" },
value: "Healthy",
},
]);
expect(status.ok).toBe(false);
if (status.ok) throw new Error("expected ambiguous status failure");
expect(status.errors[0]).toMatchObject({
code: "ambiguous_target",
operationIndex: 0,
path: "/statusStrips",
});
});
test("rejects create_dashboard when it is not the first operation", () => {
const result = previewDashboardChanges(genericDashboardFixture, [
{
type: "remove_item",
target: { kind: "telemetry", id: "queue-depth" },
},
{
type: "create_dashboard",
document: genericDashboardFixture,
},
]);
expect(result.ok).toBe(false);
if (result.ok) throw new Error("expected sequence failure");
expect(result.errors[0]).toMatchObject({
code: "invalid_operation_sequence",
operationIndex: 1,
path: "/1",
});
});
test("publishes valid operations as a persisted dashboard revision", async () => {
const { dbPath, store } = await createTestStore();
const seed = store.seedDashboardIfEmpty(genericDashboardFixture, {
actor: "seed",
message: "initial dashboard",
});
const result = publishDashboardChanges(store, exampleOperations(), {
actor: "agent",
message: "add edge router",
});
expect(result.ok).toBe(true);
if (!result.ok) throw new Error("expected publish success");
expect(existsSync(dbPath)).toBe(true);
expect(result.revision.operation).toBe("commit");
expect(result.revision.actor).toBe("agent");
expect(result.revision.message).toBe("add edge router");
expect(result.previousRevisionId).toBe(seed.id);
expect(result.patch.length).toBeGreaterThan(0);
expect(store.getActiveDashboard()?.document.serviceGroups[0]?.id).toBe("edge");
expect(store.listRevisions()).toHaveLength(2);
});
test("does not publish invalid operations", async () => {
const { store } = await createTestStore();
const seed = store.seedDashboardIfEmpty(genericDashboardFixture, {
actor: "seed",
});
const result = publishDashboardChanges(store, [
{
type: "remove_item",
target: { kind: "telemetry", id: "missing-metric" },
},
]);
expect(result.ok).toBe(false);
if (result.ok) throw new Error("expected publish failure");
expect(result.errors[0]).toMatchObject({
code: "item_not_found",
operationIndex: 0,
});
expect(store.getActiveDashboard()?.currentRevisionId).toBe(seed.id);
expect(store.listRevisions()).toHaveLength(1);
});
test("rolls back through the agent-safe revision path", async () => {
const { store } = await createTestStore();
const seed = store.seedDashboardIfEmpty(genericDashboardFixture, {
actor: "seed",
});
const publish = publishDashboardChanges(store, exampleOperations(), {
actor: "agent",
});
expect(publish.ok).toBe(true);
const rollback = rollbackDashboardRevision(store, seed.id, {
actor: "agent",
message: "restore previous dashboard",
});
expect(rollback.operation).toBe("rollback");
expect(rollback.actor).toBe("agent");
expect(rollback.sourceRevisionId).toBe(seed.id);
expect(store.getActiveDashboard()?.document.metadata.title).toBe("Operations Console");
expect(store.listRevisions().map((revision) => revision.operation)).toEqual([
"rollback",
"commit",
"seed",
]);
});
test("authenticates agent configuration requests with a shared token", () => {
const authorized = new Request("https://dimensionlab.test/api/agent/dashboard", {
headers: { authorization: "Bearer shared-secret" },
});
const rejected = new Request("https://dimensionlab.test/api/agent/dashboard");
expect(authorizeAgentConfigRequest(authorized, "shared-secret")).toEqual({
ok: true,
});
expect(() => authorizeAgentConfigRequest(rejected, "shared-secret")).toThrow(
AgentConfigAuthorizationError,
);
expect(() => authorizeAgentConfigRequest(authorized, "")).toThrow(
AgentConfigAuthorizationError,
);
});
test("handles preview, publish, and rollback requests through the HTTP adapter", async () => {
const { store } = await createTestStore();
const seed = store.seedDashboardIfEmpty(genericDashboardFixture, {
actor: "seed",
});
const preview = await handleAgentDashboardRequest(
jsonRequest({
action: "preview_changes",
operations: exampleOperations(),
}),
{ store, token: "shared-secret" },
);
expect(preview.status).toBe(200);
expect(await preview.json()).toMatchObject({
ok: true,
action: "preview_changes",
patch: expect.any(Array),
});
expect(store.listRevisions()).toHaveLength(1);
const publish = await handleAgentDashboardRequest(
jsonRequest({
action: "publish_changes",
actor: "agent",
message: "publish edge router",
operations: exampleOperations(),
}),
{ store, token: "shared-secret" },
);
expect(publish.status).toBe(200);
expect(await publish.json()).toMatchObject({
ok: true,
action: "publish_changes",
revision: { operation: "commit", actor: "agent" },
});
expect(store.listRevisions()).toHaveLength(2);
const rollback = await handleAgentDashboardRequest(
jsonRequest({
action: "rollback_revision",
actor: "agent",
revisionId: seed.id,
}),
{ store, token: "shared-secret" },
);
expect(rollback.status).toBe(200);
expect(await rollback.json()).toMatchObject({
ok: true,
action: "rollback_revision",
revision: { operation: "rollback", sourceRevisionId: seed.id },
});
});
test("returns a structured error for unknown rollback revisions", async () => {
const { store } = await createTestStore();
store.seedDashboardIfEmpty(genericDashboardFixture, {
actor: "seed",
});
const rollback = await handleAgentDashboardRequest(
jsonRequest({
action: "rollback_revision",
actor: "agent",
revisionId: "missing-revision",
}),
{ store, token: "shared-secret" },
);
expect(rollback.status).toBe(404);
expect(await rollback.json()).toMatchObject({
ok: false,
errors: [
{
code: "revision_not_found",
path: "/revisionId",
},
],
});
});
test("previews create_dashboard requests against an empty store", async () => {
const { store } = await createTestStore();
const preview = await handleAgentDashboardRequest(
jsonRequest({
action: "preview_changes",
operations: [
{
type: "create_dashboard",
document: genericDashboardFixture,
},
],
}),
{ store, token: "shared-secret" },
);
expect(preview.status).toBe(200);
expect(await preview.json()).toMatchObject({
ok: true,
action: "preview_changes",
document: {
metadata: {
title: "Operations Console",
},
},
});
expect(store.listRevisions()).toHaveLength(0);
});
});
async function createTestStore() {
const root = await mkdtemp(join(tmpdir(), "dimensionlab-agent-config-"));
tempRoots.push(root);
const dbPath = join(root, "dashboard.sqlite");
const store = createDashboardStore({
databaseUrl: `file:${dbPath}`,
});
stores.push(store);
return { dbPath, store };
}
function exampleOperations(): AgentDashboardOperation[] {
return [
{
type: "add_section",
section: {
id: "edge",
title: "Edge",
layout: "list",
},
},
{
type: "add_service",
groupId: "edge",
service: exampleService(),
},
{
type: "add_metric_card",
card: {
id: "edge-latency",
label: "Edge Latency",
value: { kind: "latency", value: 12, precision: 0 },
severity: "ok",
detail: "p95",
datasource: {
type: "external",
adapter: "prometheus",
reference: "histogram_quantile(0.95, edge_request_duration_seconds_bucket)",
},
},
position: { afterId: "service-uptime" },
},
{
type: "connect_datasource",
target: {
kind: "service",
groupId: "edge",
id: "edge-router",
},
datasource: {
type: "external",
adapter: "http-status",
reference: "GET https://edge.example.test/api/status",
},
},
{
type: "set_status_rule",
target: { kind: "telemetry", id: "edge-latency" },
thresholds: { warning: 50, danger: 100 },
},
{
type: "arrange_item",
area: "serviceGroups",
id: "edge",
index: 0,
},
];
}
function exampleService() {
return {
id: "edge-router",
label: "Edge Router",
description: "Ingress and routing",
icon: "mdi:router-network",
severity: "ok" as const,
detail: "pending datasource",
datasource: { type: "placeholder" as const, reason: "health adapter pending" },
link: {
href: "https://edge.example.test",
label: "Open Edge Router",
external: true,
},
};
}
function documentWithDuplicateNestedIds(): DashboardDocument {
const document = structuredClone(genericDashboardFixture);
document.layout.serviceGroups.push("secondary-services");
document.serviceGroups.push({
id: "secondary-services",
title: "Secondary Services",
layout: "list",
services: [
{
...document.serviceGroups[0].services[0],
label: "Shadow Identity",
},
],
});
document.layout.statusStrips.push("secondary-runtime");
document.statusStrips.push({
id: "secondary-runtime",
items: [
{
...document.statusStrips[0].items[0],
value: "Healthy",
},
],
});
return document;
}
function applyJsonPatch<T>(value: T, patch: JsonPatchOperation[]): T {
const next = structuredClone(value);
for (const operation of patch) {
const { parent, key } = jsonPointerTarget(next, operation.path);
if (operation.op === "remove") {
if (Array.isArray(parent)) {
parent.splice(Number(key), 1);
} else {
delete parent[key];
}
} else if (operation.op === "add") {
if (Array.isArray(parent)) {
parent.splice(Number(key), 0, operation.value);
} else {
parent[key] = operation.value;
}
} else if (operation.op === "replace") {
if (Array.isArray(parent)) {
parent[Number(key)] = operation.value;
} else {
parent[key] = operation.value;
}
}
}
return next;
}
function jsonPointerTarget(value: unknown, path: string) {
const segments = path
.split("/")
.slice(1)
.map((segment) => segment.replace(/~1/g, "/").replace(/~0/g, "~"));
const key = segments.pop();
if (key === undefined) throw new Error(`Invalid JSON pointer: ${path}`);
let parent = value as Record<string, unknown> | unknown[];
for (const segment of segments) {
parent = Array.isArray(parent)
? (parent[Number(segment)] as Record<string, unknown> | unknown[])
: (parent[segment] as Record<string, unknown> | unknown[]);
}
return { parent, key };
}
function jsonRequest(body: unknown) {
return new Request("https://dimensionlab.test/api/agent/dashboard", {
body: JSON.stringify(body),
headers: {
authorization: "Bearer shared-secret",
"content-type": "application/json",
},
method: "POST",
});
}

File diff suppressed because it is too large Load diff

View file

@ -1,139 +0,0 @@
import { rmSync } from "node:fs";
import { mkdtemp } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { afterEach, describe, expect, test } from "vitest";
import { dimensionLabDashboardFixture } from "$lib/dashboard-seed/dimensionlab";
import { genericDashboardFixture } from "@dimensionlab/dashboard-model/fixtures";
import { createDashboardStore, type DashboardStore } from "./db/dashboard-store";
import { loadDashboardRuntime } from "./dashboard";
const stores: DashboardStore[] = [];
const tempRoots: string[] = [];
afterEach(() => {
stores.splice(0).forEach((store) => store.close());
tempRoots.splice(0).forEach((path) => rmSync(path, { force: true, recursive: true }));
});
describe("dashboard runtime loader", () => {
test("seeds and loads the active dashboard from sqlite", async () => {
const store = await createTestStore();
const runtime = loadDashboardRuntime(store, { seedIfEmpty: true });
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);
});
test("refreshes an existing initial seed when the bundled seed changes", async () => {
const store = await createTestStore();
const oldSeed = olderDimensionLabSeed();
store.seedDashboardIfEmpty(oldSeed, {
actor: "initial-seed",
message: "load initial dashboard document",
});
const runtime = loadDashboardRuntime(store, {
refreshSeedDocument: true,
seedDocument: dimensionLabDashboardFixture,
seedIfEmpty: true,
});
expect(runtime.state).toBe("ready");
if (runtime.state !== "ready") throw new Error("expected ready dashboard");
expect(runtime.document.statusStrips[0]?.items.map((item) => item.id)).toContain(
"auto-refresh",
);
expect(store.listRevisions()).toHaveLength(2);
expect(store.getActiveDashboard()?.revision.actor).toBe("initial-seed");
});
test("does not refresh a dashboard after a user-authored revision", async () => {
const store = await createTestStore();
const oldSeed = olderDimensionLabSeed();
store.seedDashboardIfEmpty(oldSeed, {
actor: "initial-seed",
message: "load initial dashboard document",
});
store.commitDashboard(
{
...oldSeed,
metadata: {
...oldSeed.metadata,
title: "Custom Dashboard",
},
},
{
actor: "agent",
message: "customize dashboard",
},
);
const runtime = loadDashboardRuntime(store, {
refreshSeedDocument: true,
seedDocument: dimensionLabDashboardFixture,
seedIfEmpty: true,
});
expect(runtime.state).toBe("ready");
if (runtime.state !== "ready") throw new Error("expected ready dashboard");
expect(runtime.document.metadata.title).toBe("Custom Dashboard");
expect(runtime.document.statusStrips[0]?.items.map((item) => item.id)).not.toContain(
"auto-refresh",
);
expect(store.listRevisions()).toHaveLength(2);
});
test("returns empty state when no dashboard is active and seeding is disabled", async () => {
const store = await createTestStore();
const runtime = loadDashboardRuntime(store);
expect(runtime.state).toBe("empty");
if (runtime.state !== "empty") throw new Error("expected empty dashboard");
expect(runtime.title).toBe("No Dashboard Model");
expect(store.listRevisions()).toHaveLength(0);
});
});
async function createTestStore() {
const root = await mkdtemp(join(tmpdir(), "dimensionlab-dashboard-runtime-"));
tempRoots.push(root);
const store = createDashboardStore({
databaseUrl: `file:${join(root, "dashboard.sqlite")}`,
});
stores.push(store);
return store;
}
function olderDimensionLabSeed() {
const document = structuredClone(dimensionLabDashboardFixture);
document.statusStrips = document.statusStrips.map((strip) => ({
...strip,
items: strip.items.filter((item) => item.id !== "auto-refresh"),
}));
return document;
}

View file

@ -1,150 +0,0 @@
import { dimensionLabDashboardFixture } from "$lib/dashboard-seed/dimensionlab";
import type { DashboardDocument } from "@dimensionlab/dashboard-model";
import {
createDashboardStore,
DashboardPersistenceValidationError,
type DashboardStore,
} from "$lib/server/db/dashboard-store";
import { UnsupportedDashboardModelVersionError } from "$lib/server/db/model-migrations";
export type DashboardRuntimeState =
| DashboardRuntimeEmpty
| DashboardRuntimeInvalid
| DashboardRuntimeLoading
| DashboardRuntimeReady;
export interface DashboardRuntimeReady {
state: "ready";
document: DashboardDocument;
schemaVersion: string;
currentRevisionId: string;
liveDatasourceHydration?: {
enabled: boolean;
};
}
export interface DashboardRuntimeEmpty {
state: "empty";
title: string;
subtitle: string;
message: string;
}
export interface DashboardRuntimeLoading {
state: "loading";
title: string;
subtitle: string;
message: string;
}
export interface DashboardRuntimeInvalid {
state: "invalid";
title: string;
subtitle: string;
message: string;
errors: string[];
}
export interface DashboardRuntimeOptions {
refreshSeedDocument?: boolean;
seedIfEmpty?: boolean;
seedDocument?: DashboardDocument;
}
export function loadDashboardRuntime(
store?: DashboardStore,
options: DashboardRuntimeOptions = {},
): DashboardRuntimeState {
const dashboardStore = store || createDashboardStore();
try {
const seedDocument = options.seedDocument || dimensionLabDashboardFixture;
const active = dashboardStore.getActiveDashboard();
if (active) {
if (
options.refreshSeedDocument &&
shouldRefreshSeedDashboard(active, seedDocument)
) {
const refreshed = dashboardStore.commitDashboard(seedDocument, {
actor: "initial-seed",
message: "refresh bundled dashboard document",
});
return readyRuntimeState(refreshed.document, refreshed.id);
}
return readyRuntimeState(active.document, active.currentRevisionId);
}
if (!options.seedIfEmpty) {
return {
state: "empty",
title: "No Dashboard Model",
subtitle: "No active document",
message: "No validated dashboard document is active yet.",
};
}
const seeded = dashboardStore.seedDashboardIfEmpty(
seedDocument,
{
actor: "initial-seed",
message: "load initial dashboard document",
},
);
return readyRuntimeState(seeded.document, seeded.id);
} 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 readyRuntimeState(
document: DashboardDocument,
currentRevisionId: string,
): DashboardRuntimeReady {
return {
state: "ready",
document,
schemaVersion: document.schemaVersion,
currentRevisionId,
};
}
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,
};
}
function shouldRefreshSeedDashboard(
active: { document: DashboardDocument; revision: { actor: string } },
seedDocument: DashboardDocument,
): boolean {
if (active.revision.actor !== "initial-seed") return false;
if (!isBundledDimensionLabSeed(active.document, seedDocument)) return false;
return JSON.stringify(active.document) !== JSON.stringify(seedDocument);
}
function isBundledDimensionLabSeed(
document: DashboardDocument,
seedDocument: DashboardDocument,
): boolean {
return (
document.metadata.title === seedDocument.metadata.title &&
document.metadata.description === seedDocument.metadata.description
);
}

View file

@ -1,334 +0,0 @@
import { describe, expect, test, vi } from "vitest";
import {
DASHBOARD_SCHEMA_VERSION,
type DashboardDocument,
} from "@dimensionlab/dashboard-model";
import { resolveDashboardDatasources, resolveDashboardTile } from ".";
describe("dashboard datasource resolution", () => {
test("hydrates telemetry, service health, weather, and summary data from live adapters", async () => {
const fetch = vi.fn(async (input: RequestInfo | URL) => {
const url = String(input);
if (url.startsWith("https://prometheus.example/api/v1/query_range")) {
return jsonResponse({
status: "success",
data: {
result: [
{
metric: { host: "linux-infra" },
values: [
[1771430000, "10"],
[1771430060, "40"],
[1771430120, "30"],
],
},
{
metric: { host: "linux-gpu" },
values: [
[1771430000, "12"],
[1771430060, "24"],
[1771430120, "36"],
],
},
],
},
});
}
if (url.startsWith("https://prometheus.example/api/v1/query")) {
const query = new URL(url).searchParams.get("query") || "";
if (query.includes("node_boot_time_seconds")) {
return prometheusVector("90061");
}
if (query.includes("node_load15")) return prometheusVector("0.40");
if (query.includes("node_load5")) return prometheusVector("0.46");
if (query.includes("node_load1")) return prometheusVector("0.34");
return jsonResponse({
status: "success",
data: {
result: [
{
metric: { host: "linux-infra", mountpoint: "/home" },
value: [1771430400, "88"],
},
],
},
});
}
if (url.startsWith("https://api.open-meteo.com/v1/forecast")) {
return jsonResponse({
current: {
apparent_temperature: 20.9,
temperature_2m: 21.4,
weather_code: 0,
wind_speed_10m: 12,
},
});
}
if (url === "https://service.example/health") {
return jsonResponse({
status: "UP",
ping: 42,
});
}
throw new Error(`Unhandled test request: ${url}`);
});
const resolved = await resolveDashboardDatasources(testDashboard(), {
fetch,
prometheusBaseUrl: "https://prometheus.example",
});
expect(fetch).toHaveBeenCalledWith(
expect.stringContaining("/api/v1/query?"),
expect.objectContaining({ cache: "no-store" }),
);
expect(fetch).toHaveBeenCalledWith(
expect.stringContaining("/api/v1/query_range?"),
expect.objectContaining({ cache: "no-store" }),
);
const telemetry = resolved.telemetry[0];
expect(telemetry.value).toEqual({ kind: "percent", value: 88 });
expect(telemetry.severity).toBe("warning");
expect(telemetry.detail).toBe("linux-infra /home");
expect(telemetry.sparkline).toEqual([12, 40, 36]);
const service = resolved.serviceGroups[0].services[0];
expect(service.severity).toBe("ok");
expect(service.detail).toBe("42 ms");
const weather = resolved.modules?.find((module) => module.id === "weather-amsterdam");
expect(weather?.value).toBe("21.4 C");
expect(weather?.detail).toBe("Clear - feels 20.9 C - wind 12 km/h");
expect(weather?.severity).toBe("ok");
const summary = resolved.modules?.find((module) => module.id === "runtime-health-summary");
expect(summary?.value).toBe("all systems operational");
expect(summary?.detail).toBe("1 service ok");
expect(summary?.severity).toBe("ok");
expect(resolved.statusStrips[0].items).toEqual([
{ id: "system-status", label: "System Status", value: "All systems operational", severity: "ok" },
{ id: "last-sync", label: "Last Sync", value: "just now", severity: "ok" },
{ id: "uptime", label: "Uptime", value: "1d 1h 1m", severity: "ok" },
{ id: "load-avg", label: "Load Avg", value: "0.34 0.46 0.40", severity: "neutral" },
{ id: "auto-refresh", label: "Auto Refresh", value: "15s", severity: "neutral" },
]);
expect(resolved).not.toBe(testDocument);
expect(testDocument.telemetry[0].value.value).toBe(1);
});
test("shares service health snapshots across aggregate tile hydration", async () => {
let resolveFetch: ((response: Response) => void) | undefined;
const fetch = vi.fn((input: RequestInfo | URL) => {
const url = String(input);
if (url !== "https://service.example/health") {
throw new Error(`Unhandled test request: ${url}`);
}
return new Promise<Response>((resolve) => {
resolveFetch = resolve;
});
});
const document = testDashboard();
const moduleTile = resolveDashboardTile(
document,
{ kind: "module", id: "runtime-health-summary" },
{ fetch },
);
const statusTile = resolveDashboardTile(
document,
{ kind: "status", stripId: "footer", id: "system-status" },
{ fetch },
);
await Promise.resolve();
expect(fetch).toHaveBeenCalledTimes(1);
resolveFetch?.(jsonResponse({ status: "UP", ping: 42 }));
expect(await moduleTile).toMatchObject({
state: "ready",
item: {
id: "runtime-health-summary",
severity: "ok",
value: "all systems operational",
},
});
expect(await statusTile).toMatchObject({
state: "ready",
item: {
id: "system-status",
severity: "ok",
value: "All systems operational",
},
});
});
test("isolates service health snapshots by datasource fetch context", async () => {
const firstFetch = vi.fn(async () =>
jsonResponse({
status: "UP",
ping: 42,
})
);
const secondFetch = vi.fn(async () =>
jsonResponse({
status: "DOWN",
ping: 0,
})
);
const document = testDashboard();
document.serviceGroups[0].services[0] = {
...document.serviceGroups[0].services[0],
id: "api-isolated",
datasource: {
type: "external",
adapter: "http-status",
reference: "GET https://service.example/isolated",
},
};
await resolveDashboardTile(
document,
{ kind: "status", stripId: "footer", id: "system-status" },
{ fetch: firstFetch },
);
const second = await resolveDashboardTile(
document,
{ kind: "status", stripId: "footer", id: "system-status" },
{ fetch: secondFetch },
);
expect(firstFetch).toHaveBeenCalledTimes(1);
expect(secondFetch).toHaveBeenCalledTimes(1);
expect(second).toMatchObject({
state: "ready",
item: {
id: "system-status",
severity: "danger",
value: "1 service down",
},
});
});
});
const testDocument = testDashboard();
function testDashboard(): DashboardDocument {
return {
schemaVersion: DASHBOARD_SCHEMA_VERSION,
metadata: {
title: "System Overview",
refreshIntervalSeconds: 15,
},
layout: {
telemetry: ["cpu"],
serviceGroups: ["services"],
statusStrips: ["footer"],
modules: ["weather-amsterdam", "runtime-health-summary"],
},
telemetry: [
{
id: "cpu",
label: "CPU",
value: { kind: "percent", value: 1 },
detail: "fallback",
severity: "stale",
thresholds: { warning: 70, danger: 90 },
datasource: {
type: "external",
adapter: "prometheus",
reference: "fixture_cpu_query",
},
sparkline: [1],
},
],
serviceGroups: [
{
id: "services",
title: "Services",
services: [
{
id: "api",
label: "API",
description: "Example API",
severity: "stale",
detail: "fallback",
datasource: {
type: "external",
adapter: "http-status",
reference: "GET https://service.example/health",
},
},
],
},
],
statusStrips: [
{
id: "footer",
items: [
{ id: "system-status", label: "System Status", value: "fallback", severity: "ok" },
{ id: "last-sync", label: "Last Sync", value: "fallback", severity: "stale" },
{ id: "uptime", label: "Uptime", value: "fallback", severity: "ok" },
{ id: "load-avg", label: "Load Avg", value: "fallback", severity: "neutral" },
{ id: "auto-refresh", label: "Auto Refresh", value: "fallback", severity: "neutral" },
],
},
],
modules: [
{
id: "weather-amsterdam",
kind: "weather",
title: "Amsterdam",
value: "fallback",
detail: "fallback",
severity: "stale",
datasource: {
type: "external",
adapter: "weather",
reference: "open-meteo:latitude=52.3676&longitude=4.9041",
},
},
{
id: "runtime-health-summary",
kind: "summary",
title: "Runtime Health",
value: "fallback",
detail: "fallback",
severity: "stale",
datasource: {
type: "placeholder",
reason: "summary pending live health aggregation",
},
},
],
};
}
function jsonResponse(payload: unknown): Response {
return new Response(JSON.stringify(payload), {
headers: { "content-type": "application/json" },
});
}
function prometheusVector(value: string): Response {
return jsonResponse({
status: "success",
data: {
result: [
{
metric: { host: "linux-infra" },
value: [1771430400, value],
},
],
},
});
}

View file

@ -1,919 +0,0 @@
import type {
DashboardDocument,
DashboardModule,
MetricValue,
ServiceEntry,
ServiceGroup,
Severity,
StatusItem,
StatusStrip,
TelemetryCard,
} from "@dimensionlab/dashboard-model";
export type DashboardTileReference =
| { kind: "telemetry"; id: string }
| { kind: "service"; groupId: string; id: string }
| { kind: "module"; id: string }
| { kind: "status"; stripId: string; id: string };
export type DashboardTileItem =
| DashboardModule
| ServiceEntry
| StatusItem
| TelemetryCard;
export type DashboardTileResolution =
| {
state: "ready";
tile: DashboardTileReference;
item: DashboardTileItem;
}
| {
state: "not_found";
tile: DashboardTileReference;
message: string;
}
| {
state: "disabled";
tile: DashboardTileReference;
message: string;
};
export interface DatasourceResolutionOptions {
fetch?: DatasourceFetch;
now?: () => number;
prometheusBaseUrl?: string;
prometheusRangeSeconds?: number;
prometheusStepSeconds?: number;
requestTimeoutMs?: number;
}
export async function resolveDashboardDatasources(
document: DashboardDocument,
options: DatasourceResolutionOptions = {},
): Promise<DashboardDocument> {
const context = datasourceContext(options);
const telemetry = await Promise.all(
document.telemetry.map((card) => resolveTelemetryCard(card, context)),
);
const serviceGroups = await Promise.all(
document.serviceGroups.map((group) => resolveServiceGroup(group, context)),
);
const modules = await resolveModules(document.modules || [], serviceGroups, context);
const statusStrips = await resolveStatusStrips(
document.statusStrips,
document.metadata.refreshIntervalSeconds,
serviceGroups,
context,
);
return {
...structuredClone(document),
telemetry,
serviceGroups,
modules,
statusStrips,
};
}
export async function resolveDashboardTile(
document: DashboardDocument,
tile: DashboardTileReference,
options: DatasourceResolutionOptions = {},
): Promise<DashboardTileResolution> {
const context = datasourceContext(options);
if (tile.kind === "telemetry") {
const card = document.telemetry.find((item) => item.id === tile.id);
if (!card) return missingTile(tile);
return {
state: "ready",
tile,
item: await resolveTelemetryCard(card, context),
};
}
if (tile.kind === "service") {
const service = document.serviceGroups
.find((group) => group.id === tile.groupId)
?.services.find((item) => item.id === tile.id);
if (!service) return missingTile(tile);
return {
state: "ready",
tile,
item: await resolveService(service, context),
};
}
if (tile.kind === "module") {
const module = document.modules?.find((item) => item.id === tile.id);
if (!module) return missingTile(tile);
const item = module.id === "runtime-health-summary"
? runtimeHealthSummary(
module,
await serviceGroupsSnapshot(document, context),
)
: await resolveModule(module, context);
return { state: "ready", tile, item };
}
const strip = document.statusStrips.find((item) => item.id === tile.stripId);
const statusItem = strip?.items.find((item) => item.id === tile.id);
if (!strip || !statusItem) return missingTile(tile);
return {
state: "ready",
tile,
item: await resolveStatusTile(
statusItem,
document.metadata.refreshIntervalSeconds,
document,
context,
),
};
}
interface DatasourceContext {
fetch: DatasourceFetch;
fetchIdentity: number;
now: () => number;
prometheusBaseUrl: string;
prometheusRangeSeconds: number;
prometheusStepSeconds: number;
requestTimeoutMs: number;
serviceGroupsSnapshot?: Promise<ServiceGroup[]>;
}
type DatasourceFetch = (input: string, init?: RequestInit) => Promise<Response>;
interface ServiceGroupsSnapshotEntry {
expiresAt: number;
snapshot: Promise<ServiceGroup[]>;
}
const serviceGroupsSnapshotTtlMs = 30_000;
const serviceGroupsSnapshotCache = new Map<string, ServiceGroupsSnapshotEntry>();
const datasourceFetchIdentities = new WeakMap<DatasourceFetch, number>();
let nextDatasourceFetchIdentity = 1;
interface PrometheusVectorResult {
metric?: Record<string, string>;
value?: [number, string];
}
interface PrometheusMatrixResult {
metric?: Record<string, string>;
values?: Array<[number, string]>;
}
function datasourceContext(options: DatasourceResolutionOptions): DatasourceContext {
const fetch = options.fetch || globalThis.fetch;
return {
fetch,
fetchIdentity: datasourceFetchIdentity(fetch),
now: options.now || Date.now,
prometheusBaseUrl:
options.prometheusBaseUrl ||
process.env.PROMETHEUS_BASE_URL ||
"https://prometheus.dimensionlab.net",
prometheusRangeSeconds: options.prometheusRangeSeconds || 60 * 60,
prometheusStepSeconds: options.prometheusStepSeconds || 120,
requestTimeoutMs: options.requestTimeoutMs || 2_500,
};
}
function datasourceFetchIdentity(fetch: DatasourceFetch): number {
const existing = datasourceFetchIdentities.get(fetch);
if (existing) return existing;
const next = nextDatasourceFetchIdentity;
nextDatasourceFetchIdentity += 1;
datasourceFetchIdentities.set(fetch, next);
return next;
}
function serviceGroupsSnapshot(
document: DashboardDocument,
context: DatasourceContext,
): Promise<ServiceGroup[]> {
if (context.serviceGroupsSnapshot) return context.serviceGroupsSnapshot;
const key = serviceGroupsSnapshotKey(document, context);
const now = context.now();
const cached = serviceGroupsSnapshotCache.get(key);
if (cached && cached.expiresAt > now) {
context.serviceGroupsSnapshot = cached.snapshot;
return cached.snapshot;
}
const snapshot = Promise.all(
document.serviceGroups.map((group) => resolveServiceGroup(group, context)),
);
context.serviceGroupsSnapshot = snapshot;
serviceGroupsSnapshotCache.set(key, {
expiresAt: now + serviceGroupsSnapshotTtlMs,
snapshot,
});
snapshot.catch(() => {
if (serviceGroupsSnapshotCache.get(key)?.snapshot === snapshot) {
serviceGroupsSnapshotCache.delete(key);
}
});
return snapshot;
}
function serviceGroupsSnapshotKey(
document: DashboardDocument,
context: DatasourceContext,
): string {
return JSON.stringify(
{
fetchIdentity: context.fetchIdentity,
prometheusBaseUrl: context.prometheusBaseUrl,
prometheusRangeSeconds: context.prometheusRangeSeconds,
prometheusStepSeconds: context.prometheusStepSeconds,
requestTimeoutMs: context.requestTimeoutMs,
serviceGroups: document.serviceGroups.map((group) => ({
id: group.id,
services: group.services.map((service) => ({
datasource: service.datasource,
id: service.id,
})),
})),
},
);
}
async function resolveTelemetryCard(
card: TelemetryCard,
context: DatasourceContext,
): Promise<TelemetryCard> {
if (card.datasource?.type !== "external" || card.datasource.adapter !== "prometheus") {
return structuredClone(card);
}
try {
const [instant, range] = await Promise.all([
prometheusQuery(card.datasource.reference, context),
prometheusRangeQuery(card.datasource.reference, context),
]);
const current = pickMaxVectorResult(instant);
const currentValue = Number(current?.value?.[1]);
const sparkline = matrixPoints(range);
if (!Number.isFinite(currentValue)) return markStale(card, "no telemetry data");
return {
...structuredClone(card),
value: metricValueWithLiveNumber(card.value, currentValue),
severity: severityForValue(currentValue, card.thresholds),
detail: prometheusMetricDetail(current?.metric || {}, card.detail),
sparkline: sparkline.length ? sparkline : card.sparkline,
};
} catch {
return markStale(card, card.detail || "telemetry unavailable");
}
}
async function resolveServiceGroup(
group: ServiceGroup,
context: DatasourceContext,
): Promise<ServiceGroup> {
return {
...structuredClone(group),
services: await Promise.all(
group.services.map((service) => resolveService(service, context)),
),
};
}
async function resolveService(
service: ServiceEntry,
context: DatasourceContext,
): Promise<ServiceEntry> {
const datasource = service.datasource;
if (datasource?.type !== "external") return structuredClone(service);
if (datasource.adapter === "http-status") {
return resolveHttpStatusService(service, datasource.reference, context);
}
if (datasource.adapter === "prometheus") {
return resolvePrometheusService(service, datasource.reference, context);
}
return structuredClone(service);
}
async function resolveHttpStatusService(
service: ServiceEntry,
reference: string,
context: DatasourceContext,
): Promise<ServiceEntry> {
const request = parseHttpStatusReference(reference);
if (!request) return structuredClone(service);
try {
const startedAt = Date.now();
const response = await fetchWithTimeout(
context.fetch,
request.url,
{
cache: "no-store",
method: request.method,
},
context.requestTimeoutMs,
);
const elapsedMs = Math.max(0, Math.round(Date.now() - startedAt));
const badge = await uptimeBadge(response);
if (badge) {
const ping = badge.ping;
const status = badge.status || "UNKNOWN";
return {
...structuredClone(service),
severity: uptimeBadgeSeverity(status),
detail: Number.isFinite(ping)
? `${Math.round(ping as number)} ms`
: status.toLowerCase(),
};
}
return {
...structuredClone(service),
severity: response.ok ? "ok" : response.status >= 500 ? "danger" : "warning",
detail: response.ok ? `${elapsedMs} ms` : `HTTP ${response.status}`,
};
} catch {
return {
...structuredClone(service),
severity: "unavailable",
detail: "unavailable",
};
}
}
async function resolvePrometheusService(
service: ServiceEntry,
reference: string,
context: DatasourceContext,
): Promise<ServiceEntry> {
try {
const result = pickMaxVectorResult(await prometheusQuery(reference, context));
const value = Number(result?.value?.[1]);
const ok = Number.isFinite(value) && value > 0;
return {
...structuredClone(service),
severity: ok ? "ok" : "unavailable",
detail: ok ? "up" : "down",
};
} catch {
return {
...structuredClone(service),
severity: "unavailable",
detail: "unavailable",
};
}
}
async function resolveModules(
modules: DashboardModule[],
serviceGroups: ServiceGroup[],
context: DatasourceContext,
): Promise<DashboardModule[]> {
const resolved = await Promise.all(
modules.map((module) => resolveModule(module, context)),
);
return resolved.map((module) =>
module.id === "runtime-health-summary"
? runtimeHealthSummary(module, serviceGroups)
: module,
);
}
async function resolveModule(
module: DashboardModule,
context: DatasourceContext,
): Promise<DashboardModule> {
if (module.datasource?.type !== "external" || module.datasource.adapter !== "weather") {
return structuredClone(module);
}
try {
const weather = await requestJson<OpenMeteoResponse>(
context.fetch,
openMeteoUrl(module.datasource.reference),
context.requestTimeoutMs,
);
const current = weather.current;
const temperature = Number(current?.temperature_2m);
if (!Number.isFinite(temperature)) {
return {
...structuredClone(module),
severity: "stale",
detail: "weather unavailable",
};
}
const apparent = Number(current?.apparent_temperature);
const wind = Number(current?.wind_speed_10m);
const condition = weatherCondition(Number(current?.weather_code));
return {
...structuredClone(module),
value: `${temperature.toFixed(1)} C`,
detail: [
condition,
Number.isFinite(apparent) ? `feels ${apparent.toFixed(1)} C` : "",
Number.isFinite(wind) ? `wind ${Math.round(wind)} km/h` : "",
].filter(Boolean).join(" - "),
severity: "ok",
};
} catch {
return {
...structuredClone(module),
severity: "stale",
detail: "weather unavailable",
};
}
}
function runtimeHealthSummary(
module: DashboardModule,
serviceGroups: ServiceGroup[],
): DashboardModule {
const services = serviceGroups.flatMap((group) => group.services);
const down = services.filter((service) =>
service.severity === "danger" || service.severity === "unavailable"
).length;
const warning = services.filter((service) => service.severity === "warning").length;
const ok = services.filter((service) => service.severity === "ok").length;
if (down > 0) {
return {
...structuredClone(module),
value: `${down} service${down === 1 ? "" : "s"} down`,
detail: `${warning} warning${warning === 1 ? "" : "s"} - ${ok} service${ok === 1 ? "" : "s"} ok`,
severity: "danger",
};
}
if (warning > 0) {
return {
...structuredClone(module),
value: `${warning} service${warning === 1 ? "" : "s"} warning`,
detail: `${ok} service${ok === 1 ? "" : "s"} ok`,
severity: "warning",
};
}
return {
...structuredClone(module),
value: "all systems operational",
detail: `${ok} service${ok === 1 ? "" : "s"} ok`,
severity: "ok",
};
}
async function resolveStatusStrips(
strips: StatusStrip[],
refreshIntervalSeconds: number | undefined,
serviceGroups: ServiceGroup[],
context: DatasourceContext,
): Promise<StatusStrip[]> {
const [uptime, loadAverage] = await Promise.all([
prometheusScalar(
'time() - node_boot_time_seconds{job="node",host="linux-infra"}',
context,
).catch(() => null),
prometheusLoadAverage(context).catch(() => null),
]);
const health = serviceHealthSummary(serviceGroups);
return strips.map((strip) => ({
...structuredClone(strip),
items: strip.items.map((item) =>
resolveStatusItem(item, {
health,
loadAverage,
refreshIntervalSeconds,
uptime,
}),
),
}));
}
function resolveStatusItem(
item: StatusItem,
values: {
health: { severity: Severity; value: string };
loadAverage: string | null;
refreshIntervalSeconds?: number;
uptime: number | null;
},
): StatusItem {
if (item.id === "system-status") {
return {
...structuredClone(item),
value: values.health.value,
severity: values.health.severity,
};
}
if (item.id === "last-sync") {
return {
...structuredClone(item),
value: "just now",
severity: "ok",
};
}
if (item.id === "uptime" && values.uptime !== null) {
return {
...structuredClone(item),
value: formatDuration(values.uptime),
severity: "ok",
};
}
if (item.id === "load-avg" && values.loadAverage) {
return {
...structuredClone(item),
value: values.loadAverage,
severity: "neutral",
};
}
if (item.id === "auto-refresh" && values.refreshIntervalSeconds) {
return {
...structuredClone(item),
value: `${values.refreshIntervalSeconds}s`,
severity: "neutral",
};
}
return structuredClone(item);
}
async function resolveStatusTile(
item: StatusItem,
refreshIntervalSeconds: number | undefined,
document: DashboardDocument,
context: DatasourceContext,
): Promise<StatusItem> {
if (item.id === "system-status") {
const resolvedGroups = await serviceGroupsSnapshot(document, context);
const health = serviceHealthSummary(resolvedGroups);
return {
...structuredClone(item),
value: health.value,
severity: health.severity,
};
}
if (item.id === "last-sync") {
return {
...structuredClone(item),
value: "just now",
severity: "ok",
};
}
if (item.id === "uptime") {
const uptime = await prometheusScalar(
'time() - node_boot_time_seconds{job="node",host="linux-infra"}',
context,
).catch(() => null);
return uptime === null
? structuredClone(item)
: {
...structuredClone(item),
value: formatDuration(uptime),
severity: "ok",
};
}
if (item.id === "load-avg") {
const loadAverage = await prometheusLoadAverage(context).catch(() => null);
return loadAverage
? {
...structuredClone(item),
value: loadAverage,
severity: "neutral",
}
: structuredClone(item);
}
if (item.id === "auto-refresh" && refreshIntervalSeconds) {
return {
...structuredClone(item),
value: `${refreshIntervalSeconds}s`,
severity: "neutral",
};
}
return structuredClone(item);
}
function serviceHealthSummary(serviceGroups: ServiceGroup[]): {
severity: Severity;
value: string;
} {
const services = serviceGroups.flatMap((group) => group.services);
const down = services.filter((service) =>
service.severity === "danger" || service.severity === "unavailable"
).length;
const warning = services.filter((service) => service.severity === "warning").length;
if (down > 0) {
return {
severity: "danger",
value: `${down} service${down === 1 ? "" : "s"} down`,
};
}
if (warning > 0) {
return {
severity: "warning",
value: `${warning} service${warning === 1 ? "" : "s"} warning`,
};
}
return {
severity: "ok",
value: "All systems operational",
};
}
async function prometheusQuery(
query: string,
context: DatasourceContext,
): Promise<PrometheusVectorResult[]> {
const url = prometheusApiUrl(context.prometheusBaseUrl, "/api/v1/query", {
query,
});
const payload = await requestJson<PrometheusResponse<PrometheusVectorResult>>(
context.fetch,
url,
context.requestTimeoutMs,
);
return payload.status === "success" ? payload.data.result || [] : [];
}
async function prometheusRangeQuery(
query: string,
context: DatasourceContext,
): Promise<PrometheusMatrixResult[]> {
const end = Math.floor(Date.now() / 1000);
const start = end - context.prometheusRangeSeconds;
const url = prometheusApiUrl(context.prometheusBaseUrl, "/api/v1/query_range", {
query,
start: String(start),
end: String(end),
step: String(context.prometheusStepSeconds),
});
const payload = await requestJson<PrometheusResponse<PrometheusMatrixResult>>(
context.fetch,
url,
context.requestTimeoutMs,
);
return payload.status === "success" ? payload.data.result || [] : [];
}
async function prometheusScalar(
query: string,
context: DatasourceContext,
): Promise<number | null> {
const result = pickMaxVectorResult(await prometheusQuery(query, context));
const value = Number(result?.value?.[1]);
return Number.isFinite(value) ? value : null;
}
async function prometheusLoadAverage(context: DatasourceContext): Promise<string | null> {
const [one, five, fifteen] = await Promise.all([
prometheusScalar('node_load1{job="node",host="linux-infra"}', context),
prometheusScalar('node_load5{job="node",host="linux-infra"}', context),
prometheusScalar('node_load15{job="node",host="linux-infra"}', context),
]);
if (one === null || five === null || fifteen === null) return null;
return [one, five, fifteen].map((value) => value.toFixed(2)).join(" ");
}
async function requestJson<T>(
fetch: DatasourceFetch,
url: string,
timeoutMs: number,
): Promise<T> {
const response = await fetchWithTimeout(
fetch,
url,
{ cache: "no-store" },
timeoutMs,
);
if (!response.ok) throw new Error(`request failed: ${response.status}`);
return response.json() as Promise<T>;
}
async function fetchWithTimeout(
fetch: DatasourceFetch,
url: string,
init: RequestInit,
timeoutMs: number,
): Promise<Response> {
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), timeoutMs);
try {
return await fetch(url, {
...init,
signal: controller.signal,
});
} finally {
clearTimeout(timeout);
}
}
function prometheusApiUrl(
baseUrl: string,
pathname: string,
params: Record<string, string>,
): string {
const url = new URL(pathname, ensureTrailingSlash(baseUrl));
Object.entries(params).forEach(([key, value]) => url.searchParams.set(key, value));
return url.toString();
}
function ensureTrailingSlash(value: string): string {
return value.endsWith("/") ? value : `${value}/`;
}
function pickMaxVectorResult(
results: PrometheusVectorResult[],
): PrometheusVectorResult | null {
return results.reduce<PrometheusVectorResult | null>((winner, item) => {
if (!winner) return item;
return Number(item.value?.[1]) > Number(winner.value?.[1]) ? item : winner;
}, null);
}
function matrixPoints(results: PrometheusMatrixResult[]): number[] {
const byTimestamp = new Map<number, number>();
for (const series of results) {
for (const [timestamp, value] of series.values || []) {
const numeric = Number(value);
if (!Number.isFinite(numeric)) continue;
const previous = byTimestamp.get(timestamp);
if (previous === undefined || numeric > previous) {
byTimestamp.set(timestamp, numeric);
}
}
}
return [...byTimestamp.entries()]
.sort(([left], [right]) => left - right)
.map(([, value]) => value);
}
function metricValueWithLiveNumber(value: MetricValue, nextValue: number): MetricValue {
if (value.kind === "text") return value;
return {
...value,
value: value.kind === "percent" ? clamp(nextValue, 0, 100) : Math.max(0, nextValue),
};
}
function severityForValue(
value: number,
thresholds: TelemetryCard["thresholds"],
): Severity {
if (thresholds?.danger !== undefined && value >= thresholds.danger) return "danger";
if (thresholds?.warning !== undefined && value >= thresholds.warning) return "warning";
return "ok";
}
function prometheusMetricDetail(
metric: Record<string, string>,
fallback = "",
): string {
const host = metric.host || metric.instance || metric.job;
const detail = metric.mountpoint || metric.name || metric.container || metric.id;
const parts = [host, detail].filter(Boolean);
if (parts.length) return parts.join(" ");
return fallback.replace(/^fallback\s*-\s*/i, "") || "telemetry";
}
function markStale(card: TelemetryCard, detail: string): TelemetryCard {
return {
...structuredClone(card),
severity: "stale",
detail,
};
}
function parseHttpStatusReference(reference: string): { method: string; url: string } | null {
const match = /^(GET|HEAD|POST)\s+(.+)$/i.exec(reference.trim());
if (!match) return null;
return { method: match[1].toUpperCase(), url: match[2] };
}
interface PrometheusResponse<T> {
status: string;
data: {
result?: T[];
};
}
interface OpenMeteoResponse {
current?: {
apparent_temperature?: number;
temperature_2m?: number;
weather_code?: number;
wind_speed_10m?: number;
};
}
interface UptimeBadgeResponse {
status?: string;
ping?: number;
}
async function uptimeBadge(response: Response): Promise<UptimeBadgeResponse | null> {
const contentType = response.headers.get("content-type") || "";
if (!contentType.includes("json")) return null;
try {
const payload = await response.clone().json() as UptimeBadgeResponse;
return typeof payload.status === "string" ? payload : null;
} catch {
return null;
}
}
function uptimeBadgeSeverity(status = ""): Severity {
const normalized = status.toUpperCase();
if (normalized === "UP") return "ok";
if (normalized === "PENDING" || normalized === "MAINTENANCE") return "warning";
return "unavailable";
}
function openMeteoUrl(reference: string): string {
const params = new URLSearchParams();
const serialized = reference.startsWith("open-meteo:")
? reference.slice("open-meteo:".length)
: reference;
new URLSearchParams(serialized).forEach((value, key) => {
params.set(key, value);
});
params.set(
"current",
"temperature_2m,apparent_temperature,weather_code,wind_speed_10m",
);
params.set("timezone", "Europe/Amsterdam");
return `https://api.open-meteo.com/v1/forecast?${params.toString()}`;
}
function weatherCondition(code: number): string {
if (code === 0) return "Clear";
if ([1, 2].includes(code)) return "Partly cloudy";
if (code === 3) return "Cloudy";
if ([45, 48].includes(code)) return "Fog";
if (code >= 51 && code <= 67) return "Rain";
if (code >= 71 && code <= 77) return "Snow";
if (code >= 80 && code <= 82) return "Showers";
if (code >= 95) return "Thunderstorm";
return "Mixed";
}
function clamp(value: number, min: number, max: number): number {
return Math.max(min, Math.min(max, value));
}
function formatDuration(totalSeconds: number): string {
const seconds = Math.max(0, Math.floor(totalSeconds));
const days = Math.floor(seconds / 86_400);
const hours = Math.floor((seconds % 86_400) / 3_600);
const minutes = Math.floor((seconds % 3_600) / 60);
return `${days}d ${hours}h ${minutes}m`;
}
function missingTile(tile: DashboardTileReference): DashboardTileResolution {
return {
state: "not_found",
tile,
message: `Dashboard tile not found: ${tileKey(tile)}`,
};
}
function tileKey(tile: DashboardTileReference): string {
if (tile.kind === "status") return `${tile.kind}:${tile.stripId}:${tile.id}`;
if (tile.kind === "service") return `${tile.kind}:${tile.groupId}:${tile.id}`;
return `${tile.kind}:${tile.id}`;
}

View file

@ -1,47 +0,0 @@
import { mkdirSync } from "node:fs";
import { dirname, resolve } from "node:path";
import { Database } from "bun:sqlite";
import { drizzle, type BunSQLiteDatabase } from "drizzle-orm/bun-sqlite";
import { dashboardDbSchema } from "./schema";
import { applyDashboardMigrations } from "./migrations";
export const DEFAULT_DATABASE_URL = "file:./data/dimensionlab.sqlite";
export type DashboardDatabase = BunSQLiteDatabase<typeof dashboardDbSchema> & {
$client: Database;
};
export interface DashboardDatabaseConnection {
db: DashboardDatabase;
sqlite: Database;
filename: string;
close(): void;
}
export function openDashboardDatabase(
databaseUrl = process.env.DATABASE_URL || DEFAULT_DATABASE_URL,
): DashboardDatabaseConnection {
const filename = resolveSqliteFilename(databaseUrl);
mkdirSync(dirname(filename), { recursive: true });
const sqlite = new Database(filename, { create: true, readwrite: true });
const db = drizzle(sqlite, { schema: dashboardDbSchema }) as DashboardDatabase;
applyDashboardMigrations(db);
return {
db,
sqlite,
filename,
close() {
sqlite.close();
},
};
}
export function resolveSqliteFilename(databaseUrl: string): string {
if (!databaseUrl.startsWith("file:")) {
throw new Error(`Only file: SQLite DATABASE_URL values are supported: ${databaseUrl}`);
}
return resolve(databaseUrl.slice("file:".length));
}

View file

@ -1,205 +0,0 @@
import { existsSync, rmSync } from "node:fs";
import { mkdtemp } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { afterEach, describe, expect, test, vi } from "vitest";
import { genericDashboardFixture } from "@dimensionlab/dashboard-model/fixtures";
import type { DashboardDocument } from "@dimensionlab/dashboard-model";
import {
DashboardPersistenceValidationError,
createDashboardStore,
type DashboardStore,
} from "./dashboard-store";
const stores: DashboardStore[] = [];
const tempRoots: string[] = [];
const originalCwd = process.cwd();
afterEach(() => {
vi.useRealTimers();
process.chdir(originalCwd);
stores.splice(0).forEach((store) => store.close());
tempRoots.splice(0).forEach((path) => rmSync(path, { force: true, recursive: true }));
});
describe("dashboard persistence store", () => {
test("seeds the first active dashboard and creates the sqlite file", async () => {
const { dbPath, store } = await createTestStore();
const seed = store.seedDashboardIfEmpty(genericDashboardFixture, {
actor: "test-seed",
message: "initial fixture",
});
expect(existsSync(dbPath)).toBe(true);
expect(seed.operation).toBe("seed");
expect(seed.actor).toBe("test-seed");
expect(seed.schemaVersion).toBe(genericDashboardFixture.schemaVersion);
expect(seed.document.metadata.title).toBe("Operations Console");
const active = store.getActiveDashboard();
expect(active?.currentRevisionId).toBe(seed.id);
expect(active?.document.metadata.title).toBe("Operations Console");
expect(store.listRevisions()).toHaveLength(1);
});
test("commits validated updates and records revision metadata", async () => {
const { store } = await createTestStore();
const seed = store.seedDashboardIfEmpty(genericDashboardFixture, {
actor: "test-seed",
});
const updated = cloneDashboard({
...genericDashboardFixture,
metadata: {
...genericDashboardFixture.metadata,
title: "Operations Console Updated",
},
});
const revision = store.commitDashboard(updated, {
actor: "agent",
message: "rename dashboard",
});
expect(revision.operation).toBe("commit");
expect(revision.actor).toBe("agent");
expect(revision.message).toBe("rename dashboard");
expect(revision.document.metadata.title).toBe("Operations Console Updated");
expect(store.getActiveDashboard()?.currentRevisionId).toBe(revision.id);
expect(store.getRevision(seed.id)?.document.metadata.title).toBe("Operations Console");
expect(store.listRevisions().map((item) => item.id)).toEqual([
revision.id,
seed.id,
]);
});
test("rejects invalid writes without changing the active revision", async () => {
const { store } = await createTestStore();
const seed = store.seedDashboardIfEmpty(genericDashboardFixture, {
actor: "test-seed",
});
const invalid = cloneDashboard({
...genericDashboardFixture,
layout: {
...genericDashboardFixture.layout,
telemetry: ["missing-card"],
},
});
expect(() =>
store.commitDashboard(invalid, {
actor: "agent",
message: "invalid update",
}),
).toThrow(DashboardPersistenceValidationError);
const active = store.getActiveDashboard();
expect(active?.currentRevisionId).toBe(seed.id);
expect(active?.document.layout.telemetry).toEqual(genericDashboardFixture.layout.telemetry);
expect(store.listRevisions()).toHaveLength(1);
});
test("loads specific revisions and rolls back to a prior valid document", async () => {
const { store } = await createTestStore();
const seed = store.seedDashboardIfEmpty(genericDashboardFixture, {
actor: "test-seed",
});
const updated = cloneDashboard({
...genericDashboardFixture,
metadata: {
...genericDashboardFixture.metadata,
title: "Changed Dashboard",
},
});
const change = store.commitDashboard(updated, {
actor: "agent",
message: "change title",
});
const rollback = store.rollbackToRevision(seed.id, {
actor: "operator",
message: "restore seed",
});
expect(store.getRevision(change.id)?.document.metadata.title).toBe("Changed Dashboard");
expect(rollback.operation).toBe("rollback");
expect(rollback.sourceRevisionId).toBe(seed.id);
expect(rollback.document.metadata.title).toBe("Operations Console");
expect(store.getActiveDashboard()?.currentRevisionId).toBe(rollback.id);
expect(store.getActiveDashboard()?.document.metadata.title).toBe("Operations Console");
expect(store.listRevisions().map((item) => item.operation)).toEqual([
"rollback",
"commit",
"seed",
]);
});
test("assigns monotonic revision timestamps for stable history ordering", async () => {
vi.useFakeTimers();
vi.setSystemTime(new Date("2026-06-18T12:00:00.000Z"));
const { store } = await createTestStore();
const seed = store.seedDashboardIfEmpty(genericDashboardFixture, {
actor: "test-seed",
});
const updated = cloneDashboard({
...genericDashboardFixture,
metadata: {
...genericDashboardFixture.metadata,
title: "Changed Dashboard",
},
});
const commit = store.commitDashboard(updated, { actor: "agent" });
const rollback = store.rollbackToRevision(seed.id, { actor: "operator" });
expect([
seed.createdAt.toISOString(),
commit.createdAt.toISOString(),
rollback.createdAt.toISOString(),
]).toEqual([
"2026-06-18T12:00:00.000Z",
"2026-06-18T12:00:00.001Z",
"2026-06-18T12:00:00.002Z",
]);
expect(store.listRevisions().map((item) => item.id)).toEqual([
rollback.id,
commit.id,
seed.id,
]);
});
test("applies migrations when launched outside the repository root", async () => {
const root = await mkdtemp(join(tmpdir(), "dimensionlab-dashboard-cwd-"));
tempRoots.push(root);
process.chdir(root);
vi.resetModules();
const { createDashboardStore: createStore } = await import("./dashboard-store");
const store = createStore({
databaseUrl: `file:${join(root, "dashboard.sqlite")}`,
});
stores.push(store);
const seed = store.seedDashboardIfEmpty(genericDashboardFixture, {
actor: "test-seed",
});
expect(seed.document.metadata.title).toBe("Operations Console");
expect(store.getActiveDashboard()?.currentRevisionId).toBe(seed.id);
});
});
async function createTestStore() {
const root = await mkdtemp(join(tmpdir(), "dimensionlab-dashboard-store-"));
tempRoots.push(root);
const dbPath = join(root, "nested", "dashboard.sqlite");
const store = createDashboardStore({
databaseUrl: `file:${dbPath}`,
});
stores.push(store);
return { dbPath, store };
}
function cloneDashboard(document: DashboardDocument): DashboardDocument {
return structuredClone(document);
}

View file

@ -1,261 +0,0 @@
import { randomUUID } from "node:crypto";
import { desc, eq } from "drizzle-orm";
import {
type DashboardDocument,
type DashboardValidationFailure,
} from "@dimensionlab/dashboard-model";
import {
type DashboardDatabaseConnection,
openDashboardDatabase,
} from "./connection";
import {
dashboardDocuments,
dashboardRevisions,
type DashboardRevisionOperation,
} from "./schema";
import { migrateDashboardDocumentForPersistence } from "./model-migrations";
const DEFAULT_DASHBOARD_ID = "primary";
const DEFAULT_ACTOR = "system";
export interface DashboardRevision {
id: string;
dashboardId: string;
schemaVersion: string;
document: DashboardDocument;
actor: string;
message: string | null;
operation: DashboardRevisionOperation;
sourceRevisionId: string | null;
createdAt: Date;
}
export interface ActiveDashboard {
dashboardId: string;
currentRevisionId: string;
document: DashboardDocument;
revision: DashboardRevision;
createdAt: Date;
updatedAt: Date;
}
export interface DashboardWriteMetadata {
actor?: string;
message?: string;
}
export interface DashboardStoreOptions {
databaseUrl?: string;
dashboardId?: string;
}
export interface DashboardStore {
getActiveDashboard(): ActiveDashboard | null;
getRevision(revisionId: string): DashboardRevision | null;
listRevisions(limit?: number): DashboardRevision[];
seedDashboardIfEmpty(
document: DashboardDocument,
metadata?: DashboardWriteMetadata,
): DashboardRevision;
commitDashboard(
document: DashboardDocument,
metadata?: DashboardWriteMetadata,
): DashboardRevision;
rollbackToRevision(
revisionId: string,
metadata?: DashboardWriteMetadata,
): DashboardRevision;
close(): void;
}
export class DashboardPersistenceValidationError extends Error {
readonly failure: DashboardValidationFailure;
constructor(failure: DashboardValidationFailure) {
super(`Invalid dashboard document: ${failure.errors.join("; ")}`);
this.name = "DashboardPersistenceValidationError";
this.failure = failure;
}
}
export class DashboardRevisionNotFoundError extends Error {
constructor(revisionId: string) {
super(`Dashboard revision not found: ${revisionId}`);
this.name = "DashboardRevisionNotFoundError";
}
}
export function createDashboardStore(
options: DashboardStoreOptions = {},
): DashboardStore {
const connection = openDashboardDatabase(options.databaseUrl);
return new SqliteDashboardStore(connection, options.dashboardId || DEFAULT_DASHBOARD_ID);
}
class SqliteDashboardStore implements DashboardStore {
constructor(
private readonly connection: DashboardDatabaseConnection,
private readonly dashboardId: string,
) {}
getActiveDashboard(): ActiveDashboard | null {
const dashboard = this.connection.db
.select()
.from(dashboardDocuments)
.where(eq(dashboardDocuments.id, this.dashboardId))
.get();
if (!dashboard?.currentRevisionId) return null;
const revision = this.getRevision(dashboard.currentRevisionId);
if (!revision) return null;
return {
dashboardId: dashboard.id,
currentRevisionId: dashboard.currentRevisionId,
document: revision.document,
revision,
createdAt: dashboard.createdAt,
updatedAt: dashboard.updatedAt,
};
}
getRevision(revisionId: string): DashboardRevision | null {
const row = this.connection.db
.select()
.from(dashboardRevisions)
.where(eq(dashboardRevisions.id, revisionId))
.get();
return row ? toRevision(row) : null;
}
listRevisions(limit = 50): DashboardRevision[] {
return this.connection.db
.select()
.from(dashboardRevisions)
.where(eq(dashboardRevisions.dashboardId, this.dashboardId))
.orderBy(desc(dashboardRevisions.createdAt))
.limit(limit)
.all()
.map(toRevision);
}
seedDashboardIfEmpty(
document: DashboardDocument,
metadata: DashboardWriteMetadata = {},
): DashboardRevision {
const active = this.getActiveDashboard();
if (active) return active.revision;
return this.writeRevision(document, "seed", metadata);
}
commitDashboard(
document: DashboardDocument,
metadata: DashboardWriteMetadata = {},
): DashboardRevision {
return this.writeRevision(document, "commit", metadata);
}
rollbackToRevision(
revisionId: string,
metadata: DashboardWriteMetadata = {},
): DashboardRevision {
const revision = this.getRevision(revisionId);
if (!revision || revision.dashboardId !== this.dashboardId) {
throw new DashboardRevisionNotFoundError(revisionId);
}
return this.writeRevision(revision.document, "rollback", metadata, revision.id);
}
close() {
this.connection.close();
}
private writeRevision(
document: DashboardDocument,
operation: DashboardRevisionOperation,
metadata: DashboardWriteMetadata,
sourceRevisionId: string | null = null,
): DashboardRevision {
const migration = migrateDashboardDocumentForPersistence(document);
if (!migration.valid) {
throw new DashboardPersistenceValidationError(migration.failure);
}
const now = this.nextRevisionTimestamp();
const revision: DashboardRevision = {
id: randomUUID(),
dashboardId: this.dashboardId,
schemaVersion: migration.document.schemaVersion,
document: structuredClone(migration.document),
actor: metadata.actor || DEFAULT_ACTOR,
message: metadata.message || null,
operation,
sourceRevisionId,
createdAt: now,
};
this.connection.db.transaction((tx) => {
tx.insert(dashboardRevisions).values({
id: revision.id,
dashboardId: revision.dashboardId,
schemaVersion: revision.schemaVersion,
document: revision.document,
actor: revision.actor,
message: revision.message,
operation: revision.operation,
sourceRevisionId: revision.sourceRevisionId,
createdAt: revision.createdAt,
}).run();
tx.insert(dashboardDocuments)
.values({
id: this.dashboardId,
currentRevisionId: revision.id,
createdAt: now,
updatedAt: now,
})
.onConflictDoUpdate({
target: dashboardDocuments.id,
set: {
currentRevisionId: revision.id,
updatedAt: now,
},
})
.run();
});
return revision;
}
private nextRevisionTimestamp(): Date {
const active = this.getActiveDashboard();
const activeUpdatedAtMs = active?.updatedAt.getTime() || 0;
return new Date(Math.max(Date.now(), activeUpdatedAtMs + 1));
}
}
type DashboardRevisionRow = typeof dashboardRevisions.$inferSelect;
function toRevision(row: DashboardRevisionRow): DashboardRevision {
const migration = migrateDashboardDocumentForPersistence(row.document);
if (!migration.valid) {
throw new DashboardPersistenceValidationError(migration.failure);
}
return {
id: row.id,
dashboardId: row.dashboardId,
schemaVersion: row.schemaVersion,
document: migration.document,
actor: row.actor,
message: row.message,
operation: row.operation as DashboardRevisionOperation,
sourceRevisionId: row.sourceRevisionId,
createdAt: row.createdAt,
};
}

View file

@ -1,52 +0,0 @@
import { existsSync } from "node:fs";
import { dirname, join, resolve } from "node:path";
import { fileURLToPath } from "node:url";
import { migrate } from "drizzle-orm/bun-sqlite/migrator";
import type { BunSQLiteDatabase } from "drizzle-orm/bun-sqlite";
const MIGRATION_JOURNAL_PATH = join("meta", "_journal.json");
export function applyDashboardMigrations(
database: BunSQLiteDatabase<Record<string, unknown>>,
migrationsFolder = resolveDashboardMigrationsFolder(),
) {
migrate(database, { migrationsFolder });
}
export function resolveDashboardMigrationsFolder(
explicitFolder = process.env.DASHBOARD_MIGRATIONS_DIR,
): string {
if (explicitFolder) return assertMigrationFolder(resolve(explicitFolder));
const cwdFolder = findMigrationFolder(process.cwd());
if (cwdFolder) return cwdFolder;
const moduleFolder = findMigrationFolder(dirname(fileURLToPath(import.meta.url)));
if (moduleFolder) return moduleFolder;
throw new Error(
"Unable to locate dashboard Drizzle migrations. Set DASHBOARD_MIGRATIONS_DIR.",
);
}
function findMigrationFolder(startPath: string): string | null {
let currentPath = resolve(startPath);
while (true) {
const candidate = join(currentPath, "drizzle");
if (hasMigrationJournal(candidate)) return candidate;
const parentPath = dirname(currentPath);
if (parentPath === currentPath) return null;
currentPath = parentPath;
}
}
function assertMigrationFolder(folder: string): string {
if (hasMigrationJournal(folder)) return folder;
throw new Error(`Dashboard migrations not found in ${folder}`);
}
function hasMigrationJournal(folder: string): boolean {
return existsSync(join(folder, MIGRATION_JOURNAL_PATH));
}

View file

@ -1,31 +0,0 @@
import { describe, expect, test } from "vitest";
import { DASHBOARD_SCHEMA_VERSION } from "@dimensionlab/dashboard-model";
import { genericDashboardFixture } from "@dimensionlab/dashboard-model/fixtures";
import {
UnsupportedDashboardModelVersionError,
migrateDashboardDocumentForPersistence,
} from "./model-migrations";
describe("dashboard model migrations", () => {
test("accepts the current dashboard model version without migration", () => {
const result = migrateDashboardDocumentForPersistence(genericDashboardFixture);
expect(result.valid).toBe(true);
if (!result.valid) throw new Error("expected current fixture to be valid");
expect(result.document).toEqual(genericDashboardFixture);
expect(result.fromVersion).toBe(DASHBOARD_SCHEMA_VERSION);
expect(result.toVersion).toBe(DASHBOARD_SCHEMA_VERSION);
expect(result.migrated).toBe(false);
});
test("fails unsupported model versions with an explicit migration error", () => {
const previousVersion = {
...genericDashboardFixture,
schemaVersion: "dashboard.v0",
};
expect(() => migrateDashboardDocumentForPersistence(previousVersion)).toThrow(
UnsupportedDashboardModelVersionError,
);
});
});

View file

@ -1,62 +0,0 @@
import {
DASHBOARD_SCHEMA_VERSION,
validateDashboardDocument,
type DashboardDocument,
type DashboardValidationFailure,
} from "@dimensionlab/dashboard-model";
export interface DashboardModelMigrationSuccess {
valid: true;
document: DashboardDocument;
fromVersion: string;
toVersion: typeof DASHBOARD_SCHEMA_VERSION;
migrated: boolean;
}
export interface DashboardModelMigrationFailure {
valid: false;
failure: DashboardValidationFailure;
}
export type DashboardModelMigrationResult =
| DashboardModelMigrationFailure
| DashboardModelMigrationSuccess;
export class UnsupportedDashboardModelVersionError extends Error {
constructor(
readonly fromVersion: string,
readonly toVersion: string,
) {
super(`No dashboard model migration from ${fromVersion} to ${toVersion}`);
this.name = "UnsupportedDashboardModelVersionError";
}
}
export function migrateDashboardDocumentForPersistence(
value: unknown,
): DashboardModelMigrationResult {
const version = readSchemaVersion(value);
if (version && version !== DASHBOARD_SCHEMA_VERSION) {
throw new UnsupportedDashboardModelVersionError(version, DASHBOARD_SCHEMA_VERSION);
}
const validation = validateDashboardDocument(value);
if (!validation.valid) {
return { valid: false, failure: validation };
}
return {
valid: true,
document: validation.data,
fromVersion: DASHBOARD_SCHEMA_VERSION,
toVersion: DASHBOARD_SCHEMA_VERSION,
migrated: false,
};
}
function readSchemaVersion(value: unknown): string | null {
if (!value || typeof value !== "object") return null;
const schemaVersion = (value as { schemaVersion?: unknown }).schemaVersion;
return typeof schemaVersion === "string" ? schemaVersion : null;
}

View file

@ -1,39 +0,0 @@
import type { DashboardDocument } from "@dimensionlab/dashboard-model";
import { index, integer, sqliteTable, text } from "drizzle-orm/sqlite-core";
export const dashboardDocuments = sqliteTable("dashboard_documents", {
id: text("id").primaryKey(),
currentRevisionId: text("current_revision_id"),
createdAt: integer("created_at", { mode: "timestamp_ms" }).notNull(),
updatedAt: integer("updated_at", { mode: "timestamp_ms" }).notNull(),
});
export const dashboardRevisions = sqliteTable(
"dashboard_revisions",
{
id: text("id").primaryKey(),
dashboardId: text("dashboard_id").notNull(),
schemaVersion: text("schema_version").notNull(),
document: text("document", { mode: "json" }).$type<DashboardDocument>().notNull(),
actor: text("actor").notNull(),
message: text("message"),
operation: text("operation", {
enum: ["seed", "commit", "rollback"],
}).notNull(),
sourceRevisionId: text("source_revision_id"),
createdAt: integer("created_at", { mode: "timestamp_ms" }).notNull(),
},
(table) => [
index("idx_dashboard_revisions_dashboard_created").on(
table.dashboardId,
table.createdAt,
),
],
);
export const dashboardDbSchema = {
dashboardDocuments,
dashboardRevisions,
};
export type DashboardRevisionOperation = "seed" | "commit" | "rollback";

View file

@ -1,55 +0,0 @@
import { setupServer } from "msw/node";
import { afterAll, afterEach, beforeAll, describe, expect, test } from "vitest";
import { externalApiHandlers } from "./external-api-mocks";
const server = setupServer(...externalApiHandlers);
describe("external API mocks", () => {
beforeAll(() => {
server.listen({ onUnhandledRequest: "error" });
});
afterEach(() => {
server.resetHandlers();
});
afterAll(() => {
server.close();
});
test("defines deterministic handlers for deferred datasource adapters", () => {
expect(externalApiHandlers).toHaveLength(3);
expect(externalApiHandlers.map((handler) => handler.info.header)).toEqual([
"GET https://prometheus.dimensionlab.net/api/v1/query",
"GET https://uptime.dimensionlab.net/api/status-page/*",
"GET https://api.open-meteo.com/v1/forecast",
]);
});
test("intercepts deferred datasource requests without live services", async () => {
const prometheus = await fetch(
"https://prometheus.dimensionlab.net/api/v1/query",
).then((response) => response.json());
const status = await fetch(
"https://uptime.dimensionlab.net/api/status-page/dimensionlab",
).then((response) => response.json());
const weather = await fetch(
"https://api.open-meteo.com/v1/forecast?latitude=52.37&longitude=4.9",
).then((response) => response.json());
expect(prometheus).toMatchObject({
status: "success",
data: { result: [{ value: [1771430400, "1"] }] },
});
expect(status).toMatchObject({
status: "ok",
incidents: [],
});
expect(weather).toMatchObject({
current: {
temperature_2m: 21.4,
weather_code: 0,
},
});
});
});

View file

@ -1,33 +0,0 @@
import { http, HttpResponse } from "msw";
export const externalApiHandlers = [
http.get("https://prometheus.dimensionlab.net/api/v1/query", () =>
HttpResponse.json({
status: "success",
data: {
resultType: "vector",
result: [
{
metric: { instance: "fixture" },
value: [1771430400, "1"],
},
],
},
}),
),
http.get("https://uptime.dimensionlab.net/api/status-page/*", () =>
HttpResponse.json({
status: "ok",
incidents: [],
monitors: [{ name: "fixture", status: "up" }],
}),
),
http.get("https://api.open-meteo.com/v1/forecast", () =>
HttpResponse.json({
current: {
temperature_2m: 21.4,
weather_code: 0,
},
}),
),
];

View file

@ -1,103 +0,0 @@
import { readFileSync } from "node:fs";
import { join } from "node:path";
import { describe, expect, test } from "vitest";
import { type DashboardDocument } from "@dimensionlab/dashboard-model";
import { dimensionLabDashboardFixture } from "$lib/dashboard-seed/dimensionlab";
import { genericDashboardFixture } from "@dimensionlab/dashboard-model/fixtures";
import { dashboardDocumentToUiDashboard } from "./model-renderer";
const appRoot = process.cwd().endsWith(`${join("apps", "web")}`)
? process.cwd()
: join(process.cwd(), "apps", "web");
describe("dashboard model renderer", () => {
test("projects the Dimension Lab model into UI component props", () => {
const dashboard = dashboardDocumentToUiDashboard(dimensionLabDashboardFixture);
expect(dashboard.title).toBe(dimensionLabDashboardFixture.metadata.title);
expect(dashboard.subtitle).toBe(dimensionLabDashboardFixture.metadata.subtitle);
expect(dashboard.telemetry.map((card) => card.id)).toEqual(
dimensionLabDashboardFixture.layout.telemetry,
);
expect(dashboard.telemetry[0]).toMatchObject({
label: "Infra RAM",
icon: "mdi:memory",
severity: "ok",
});
expect(dashboard.serviceGroups.map((group) => group.id)).toEqual(
dimensionLabDashboardFixture.layout.serviceGroups,
);
expect(dashboard.serviceGroups.find((group) => group.id === "runtime-health")?.layout).toBe(
"grid",
);
expect(dashboard.modules.map((module) => module.id)).toEqual([
"weather-amsterdam",
"runtime-health-summary",
]);
expect(dashboard.statusItems.map((item) => item.id)).toEqual([
"footer-status:system-status",
"footer-status:last-sync",
"footer-status:uptime",
"footer-status:load-avg",
"footer-status:auto-refresh",
]);
expect(dashboard.statusStripId).toBe("footer-status");
});
test("projects a second fixture through the same mapper", () => {
const dashboard = dashboardDocumentToUiDashboard(genericDashboardFixture);
expect(dashboard.title).toBe("Operations Console");
expect(dashboard.telemetry.map((card) => card.id)).toEqual([
"service-uptime",
"queue-depth",
]);
expect(dashboard.serviceGroups[0]?.services.map((service) => service.id)).toEqual([
"identity",
"scheduler",
]);
expect(dashboard.modules[0]).toMatchObject({
id: "ambient",
title: "Environment",
icon: "mdi:radar",
});
});
test("returns intentional empty arrays for missing optional sections", () => {
const emptyModel: DashboardDocument = {
...genericDashboardFixture,
layout: {
density: "compact",
telemetry: [],
serviceGroups: [],
statusStrips: [],
modules: [],
},
telemetry: [],
serviceGroups: [],
statusStrips: [],
modules: [],
};
const dashboard = dashboardDocumentToUiDashboard(emptyModel);
expect(dashboard.telemetry).toEqual([]);
expect(dashboard.serviceGroups).toEqual([]);
expect(dashboard.statusItems).toEqual([]);
expect(dashboard.modules).toEqual([]);
});
test("does not hardcode environment-specific content in mapper source", () => {
const source = readFileSync(
join(appRoot, "src/lib/ui-adapter/model-renderer.ts"),
"utf8",
)
.toLowerCase()
.replaceAll("@dimensionlab/dashboard-model", "@internal/dashboard-model")
.replaceAll("@dimensionlab/ui", "@internal/ui");
expect(source).not.toContain("dimension");
expect(source).not.toContain("vaultwarden");
expect(source).not.toContain("forgejo");
});
});

View file

@ -1,111 +0,0 @@
import type {
DashboardDocument,
DashboardModule,
ServiceEntry,
ServiceGroup,
StatusItem,
StatusStrip,
TelemetryCard,
} from "@dimensionlab/dashboard-model";
import type {
UiDashboardPreview,
UiModuleBlock,
UiServiceGroup,
UiServiceRow,
UiStatusItem,
UiTelemetryCard,
} from "@dimensionlab/ui";
export function dashboardDocumentToUiDashboard(
document: DashboardDocument,
): UiDashboardPreview {
return {
eyebrow: document.schemaVersion,
title: document.metadata.title,
subtitle: document.metadata.subtitle,
telemetry: orderedItems(document.layout.telemetry, document.telemetry).map(
telemetryToUi,
),
serviceGroups: orderedItems(
document.layout.serviceGroups,
document.serviceGroups,
).map(serviceGroupToUi),
modules: orderedItems(
document.layout.modules || [],
document.modules || [],
).map(moduleToUi),
statusItems: orderedItems(
document.layout.statusStrips,
document.statusStrips,
).flatMap(statusStripToUiItems),
statusStripId: document.layout.statusStrips[0],
};
}
function telemetryToUi(card: TelemetryCard): UiTelemetryCard {
return {
id: card.id,
label: card.label,
description: card.description,
icon: card.icon,
value: card.value,
detail: card.detail,
severity: card.severity,
sparkline: card.sparkline,
};
}
function serviceGroupToUi(group: ServiceGroup): UiServiceGroup {
return {
id: group.id,
layout: group.layout,
title: group.title,
services: group.services.map(serviceToUi),
};
}
function serviceToUi(service: ServiceEntry): UiServiceRow {
return {
id: service.id,
label: service.label,
description: service.description,
icon: service.icon,
severity: service.severity,
detail: service.detail,
link: service.link,
};
}
function moduleToUi(module: DashboardModule): UiModuleBlock {
return {
id: module.id,
title: module.title,
label: module.label,
value: module.value,
detail: module.detail,
icon: module.icon,
severity: module.severity,
};
}
function statusStripToUiItems(strip: StatusStrip): UiStatusItem[] {
return strip.items.map((item) => statusItemToUi(strip.id, item));
}
function statusItemToUi(stripId: string, item: StatusItem): UiStatusItem {
return {
id: `${stripId}:${item.id}`,
label: item.label,
value: item.value,
severity: item.severity,
link: item.link,
};
}
function orderedItems<T extends { id: string }>(ids: string[], items: T[]): T[] {
const byId = new Map(items.map((item) => [item.id, item]));
return ids.flatMap((id) => {
const item = byId.get(id);
return item ? [item] : [];
});
}

View file

@ -1,578 +0,0 @@
import { spawnSync } from "node:child_process";
import { chmodSync, existsSync, mkdtempSync, readFileSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { describe, expect, test } from "vitest";
const root = existsSync(join(process.cwd(), "turbo.json"))
? process.cwd()
: join(process.cwd(), "..", "..");
describe("workspace boundaries", () => {
test("declares the root as a turbo-managed bun workspace", () => {
const packageJson = JSON.parse(
readFileSync(join(root, "package.json"), "utf8"),
) as {
private?: boolean;
scripts?: Record<string, string>;
workspaces?: string[];
};
expect(packageJson.private).toBe(true);
expect(packageJson.workspaces).toEqual(["apps/*", "packages/*"]);
expect(packageJson.scripts?.build).toBe("turbo run build");
expect(existsSync(join(root, "turbo.json"))).toBe(true);
});
test("keeps release orchestration in the root turbo task graph", () => {
const rootPackage = JSON.parse(
readFileSync(join(root, "package.json"), "utf8"),
) as {
scripts?: Record<string, string>;
};
const webPackage = JSON.parse(
readFileSync(join(root, "apps/web/package.json"), "utf8"),
) as { scripts?: Record<string, string> };
const turboConfig = JSON.parse(
readFileSync(join(root, "turbo.json"), "utf8"),
) as {
globalDependencies?: string[];
tasks?: Record<string, { env?: string[] }>;
};
expect(rootPackage.scripts?.["test:qa"]).toBe(
"turbo run check test:unit build @dimensionlab/ui#build-storybook @dimensionlab/web#test:e2e",
);
expect(webPackage.scripts).not.toHaveProperty("test:qa");
expect(webPackage.scripts?.test).toBe("bun --bun vitest run");
expect(webPackage.scripts?.["test:unit"]).toBe("bun --bun vitest run");
expect(turboConfig.tasks).not.toHaveProperty("test:qa");
expect(turboConfig.globalDependencies).toEqual(
expect.arrayContaining(["bun.lock", "tsconfig.base.json"]),
);
expect(turboConfig.tasks?.["test:e2e"]?.env).toEqual(
expect.arrayContaining([
"CI",
"PLAYWRIGHT_DATABASE_URL",
"PLAYWRIGHT_PORT",
"PLAYWRIGHT_STORYBOOK_PORT",
]),
);
});
test("lets turbo build app and Storybook artifacts before e2e serves them", () => {
const playwrightConfig = readFileSync(
join(root, "apps/web/playwright.config.ts"),
"utf8",
);
const turboConfig = JSON.parse(
readFileSync(join(root, "turbo.json"), "utf8"),
) as {
tasks?: Record<string, { dependsOn?: string[] }>;
};
expect(playwrightConfig).not.toContain("bun run build &&");
expect(playwrightConfig).not.toContain("bun run build-storybook");
expect(turboConfig.tasks?.["test:e2e"]?.dependsOn).toEqual(
expect.arrayContaining(["@dimensionlab/ui#build-storybook"]),
);
});
test("accounts for local env files in cacheable Vite and Storybook task hashes", () => {
const turboConfig = JSON.parse(
readFileSync(join(root, "turbo.json"), "utf8"),
) as {
tasks?: Record<string, { inputs?: string[] }>;
};
for (const taskName of ["build", "build-storybook"]) {
expect(turboConfig.tasks?.[taskName]?.inputs).toEqual([
"$TURBO_DEFAULT$",
".env*",
]);
}
});
test("keeps the website app and reusable UI library as separate packages", () => {
const webPackage = JSON.parse(
readFileSync(join(root, "apps/web/package.json"), "utf8"),
) as { dependencies?: Record<string, string>; name?: string };
const uiPackage = JSON.parse(
readFileSync(join(root, "packages/ui/package.json"), "utf8"),
) as { exports?: Record<string, unknown>; name?: string };
expect(webPackage.name).toBe("@dimensionlab/web");
expect(webPackage.dependencies?.["@dimensionlab/ui"]).toBe("workspace:*");
expect(uiPackage.name).toBe("@dimensionlab/ui");
expect(uiPackage.exports).toHaveProperty(".");
expect(uiPackage.exports).toHaveProperty("./styles.css");
});
test("keeps the dashboard model in a reusable internal package", () => {
const webPackage = JSON.parse(
readFileSync(join(root, "apps/web/package.json"), "utf8"),
) as { dependencies?: Record<string, string> };
const modelPackage = JSON.parse(
readFileSync(join(root, "packages/dashboard-model/package.json"), "utf8"),
) as { exports?: Record<string, unknown>; name?: string };
const tsconfig = JSON.parse(
readFileSync(join(root, "tsconfig.json"), "utf8"),
) as { references?: Array<{ path: string }> };
expect(webPackage.dependencies?.["@dimensionlab/dashboard-model"]).toBe(
"workspace:*",
);
expect(modelPackage.name).toBe("@dimensionlab/dashboard-model");
expect(modelPackage.exports).toHaveProperty(".");
expect(modelPackage.exports).toHaveProperty("./fixtures");
expect(tsconfig.references).toEqual(
expect.arrayContaining([{ path: "./packages/dashboard-model" }]),
);
expect(existsSync(join(root, "apps/web/src/lib/model/index.ts"))).toBe(
false,
);
});
test("consumes workspace packages through package exports instead of source aliases", () => {
const webTsconfig = JSON.parse(
readFileSync(join(root, "apps/web/tsconfig.json"), "utf8"),
) as { compilerOptions?: { paths?: Record<string, string[]> } };
const modelPackage = JSON.parse(
readFileSync(join(root, "packages/dashboard-model/package.json"), "utf8"),
) as { exports?: Record<string, WorkspacePackageExport> };
const uiPackage = JSON.parse(
readFileSync(join(root, "packages/ui/package.json"), "utf8"),
) as { exports?: Record<string, WorkspacePackageExport> };
const viteConfig = readFileSync(join(root, "apps/web/vite.config.ts"), "utf8");
const turboConfig = JSON.parse(
readFileSync(join(root, "turbo.json"), "utf8"),
) as { tasks?: Record<string, { dependsOn?: string[] }> };
expect(webTsconfig.compilerOptions?.paths).not.toHaveProperty(
"@dimensionlab/dashboard-model",
);
expect(webTsconfig.compilerOptions?.paths).not.toHaveProperty(
"@dimensionlab/dashboard-model/fixtures",
);
expect(webTsconfig.compilerOptions?.paths).not.toHaveProperty("@dimensionlab/ui");
expect(webTsconfig.compilerOptions?.paths).not.toHaveProperty(
"@dimensionlab/ui/styles.css",
);
expect(webTsconfig.compilerOptions?.paths).toEqual({
"$lib/*": ["src/lib/*"],
});
expect(viteConfig).not.toContain("../../packages/dashboard-model/src");
expect(viteConfig).not.toContain("../../packages/ui/src");
expect(turboConfig.tasks?.dev?.dependsOn).toEqual(["^build"]);
expectPackageExport(modelPackage.exports?.["."], {
types: "./dist/index.d.ts",
development: "./src/index.ts",
default: "./dist/index.js",
});
expectPackageExport(modelPackage.exports?.["./fixtures"], {
types: "./dist/fixtures/index.d.ts",
development: "./src/fixtures/index.ts",
default: "./dist/fixtures/index.js",
});
expectPackageExport(uiPackage.exports?.["."], {
types: "./dist/index.d.ts",
development: "./src/index.ts",
default: "./dist/index.js",
});
expectPackageExport(uiPackage.exports?.["./styles.css"], {
development: "./src/styles.css",
default: "./dist/styles.css",
});
expectPackageExport(uiPackage.exports?.["./tokens.css"], {
development: "./src/tokens.css",
default: "./dist/tokens.css",
});
});
test("builds the internal container from a turbo-pruned web workspace", () => {
const containerfile = readFileSync(
join(root, "apps/web/Containerfile"),
"utf8",
);
const pruneCommand = "turbo prune @dimensionlab/web --docker";
const jsonCopy = "COPY --from=pruner /repo/out/json/ ./";
const sourceCopy = "COPY --from=pruner /repo/out/full/ ./";
const tsconfigCopy =
"COPY --from=pruner /repo/tsconfig.base.json /repo/tsconfig.json ./";
const installCommand = "RUN bun install --frozen-lockfile --ignore-scripts";
expect(containerfile).toContain("AS pruner");
expect(containerfile).toContain(pruneCommand);
expect(containerfile).toContain(jsonCopy);
expect(containerfile).toContain(sourceCopy);
expect(containerfile).toContain(tsconfigCopy);
expect(containerfile).not.toContain(
"COPY packages/dashboard-model/package.json",
);
expect(containerfile).not.toContain("COPY packages/ui/package.json");
const jsonCopyIndex = containerfile.indexOf(jsonCopy);
const installIndex = containerfile.indexOf(installCommand);
const sourceCopyIndex = containerfile.indexOf(sourceCopy);
const tsconfigCopyIndex = containerfile.indexOf(tsconfigCopy);
expect(jsonCopyIndex).toBeGreaterThan(-1);
expect(installIndex).toBeGreaterThan(jsonCopyIndex);
expect(sourceCopyIndex).toBeGreaterThan(installIndex);
expect(tsconfigCopyIndex).toBeGreaterThan(sourceCopyIndex);
expect(containerfile.indexOf("RUN bun run build")).toBeGreaterThan(
tsconfigCopyIndex,
);
});
test("keeps local turbo prune output out of git and container contexts", () => {
const webPackage = JSON.parse(
readFileSync(join(root, "apps/web/package.json"), "utf8"),
) as { scripts?: Record<string, string> };
const gitignore = readFileSync(join(root, ".gitignore"), "utf8");
const containerignore = readFileSync(join(root, ".containerignore"), "utf8");
const containerIgnoreRules = new Set(
containerignore
.split(/\r?\n/)
.map((line) => line.trim())
.filter(Boolean),
);
expect(gitignore).toContain("out/");
expect(containerignore).toContain("out");
expect([...containerIgnoreRules]).toEqual(
expect.arrayContaining([
"apps/*/.turbo",
"apps/*/build",
"apps/*/data",
"apps/*/dist",
"apps/*/playwright-report",
"apps/*/test-results",
"packages/*/.turbo",
"packages/*/dist",
"packages/*/storybook-static",
]),
);
expect(webPackage.scripts?.build).toBe(
"rm -rf build && vite build && bun build src/server/index.ts --target bun --outdir build",
);
});
test("defines Forgejo CI and main-branch deploy automation", () => {
const workflow = readFileSync(
join(root, ".forgejo/workflows/dimensionlab-website.yml"),
"utf8",
);
expect(workflow).toContain("name: Dimension Lab website");
expect(workflow).toContain("pull_request:");
expect(workflow).toContain("push:");
expect(workflow).toContain("branches:");
expect(workflow).toContain("- main");
expect(workflow).toContain("runs-on: docker");
expect(workflow).toContain("bun install --frozen-lockfile");
expect(workflow).toContain("bun run check");
expect(workflow).toContain("bun run test");
expect(workflow).toContain("bun run build");
expect(workflow).toContain("needs: ci");
expect(workflow).toContain("runs-on: deploy");
expect(workflow).toContain("github.event_name == 'push'");
expect(workflow).toContain("github.ref == 'refs/heads/main'");
expect(workflow).toContain(
"git remote add origin git@git.dimensionlab.net:vince/dimensionlab-website.git",
);
expect(workflow).toContain('git fetch --force --prune --depth=1 origin "$GITHUB_SHA"');
expect(workflow).toContain("podman inspect dimensionlab-website");
expect(workflow).toContain("DEPLOY_CONTAINER_CLI: podman");
expect(workflow).toContain("PODMAN_SYSTEMD_UNIT");
expect(workflow).toContain("scripts/deploy-dimensionlab-website.sh");
});
test("keeps production deployment behind a guarded script", () => {
const deployScript = readFileSync(
join(root, "scripts/deploy-dimensionlab-website.sh"),
"utf8",
);
expect(deployScript).toContain("refs/heads/main");
expect(deployScript).toContain("dimensionlab-website.service");
expect(deployScript).toContain("localhost/dimensionlab-website");
expect(deployScript).toContain("apps/web/Containerfile");
expect(deployScript).toContain("rollback-");
expect(deployScript).toContain("https://dimensionlab.net");
expect(deployScript).toContain("/api/dashboard/tiles");
expect(deployScript).toContain("--dry-run");
expect(deployScript).toContain("DEPLOY_RESTART_STRATEGY");
});
test("rolls back the latest image when production smoke checks fail", () => {
const result = runDeployScriptWithFakes(
{
curl: failingCurlCommand,
git: fakeGitCommand,
podman: fakePodmanCommand("dimensionlab-website.service"),
},
{
DEPLOY_CONTAINER_CLI: "podman",
DEPLOY_EVENT_NAME: "push",
DEPLOY_REF: "refs/heads/main",
DEPLOY_RESTART_STRATEGY: "quadlet-container",
DEPLOY_SHA: "1234567890abcdef",
DEPLOY_SMOKE_TIMEOUT_SECONDS: "0",
},
);
expect(result.status).toBe(1);
expect(result.stderr).toContain("smoke checks failed");
expect(result.stderr).toContain("rolling back to localhost/dimensionlab-website:rollback-");
expect(result.log).toMatch(
/tag localhost\/dimensionlab-website:latest localhost\/dimensionlab-website:rollback-\d{14}/,
);
expect(result.log).toMatch(
/tag localhost\/dimensionlab-website:rollback-\d{14} localhost\/dimensionlab-website:latest/,
);
expect(result.log.match(/^stop dimensionlab-website$/gm)).toHaveLength(2);
});
test("rolls back the latest image when the container fails to restart", () => {
const result = runDeployScriptWithFakes(
{
curl: passingCurlCommand,
git: fakeGitCommand,
podman: fakePodmanCommand("dimensionlab-website.service"),
},
{
DEPLOY_CONTAINER_CLI: "podman",
DEPLOY_CONTAINER_START_TIMEOUT_SECONDS: "1",
DEPLOY_TEST_CONTAINER_IMAGE: "wrong",
DEPLOY_EVENT_NAME: "push",
DEPLOY_REF: "refs/heads/main",
DEPLOY_RESTART_STRATEGY: "quadlet-container",
DEPLOY_SHA: "1234567890abcdef",
},
);
expect(result.status).toBe(1);
expect(result.stderr).toContain("did not restart on localhost/dimensionlab-website:latest");
expect(result.stderr).toContain("rolling back to localhost/dimensionlab-website:rollback-");
expect(result.stderr).toContain("rollback image is running");
expect(result.log).toMatch(
/tag localhost\/dimensionlab-website:rollback-\d{14} localhost\/dimensionlab-website:latest/,
);
expect(result.log.match(/^stop dimensionlab-website$/gm)).toHaveLength(2);
});
test.each([
{
env: { DEPLOY_EVENT_NAME: "pull_request", DEPLOY_REF: "refs/heads/main" },
message: "production deploys only run for push",
},
{
env: { DEPLOY_EVENT_NAME: "push", DEPLOY_REF: "refs/heads/codex/test" },
message: "expected refs/heads/main",
},
])("refuses guarded deploy contexts before host mutations", ({ env, message }) => {
const result = runDeployScriptWithFakes(
{
curl: passingCurlCommand,
git: fakeGitCommand,
podman: fakePodmanCommand("dimensionlab-website.service"),
},
{
DEPLOY_CONTAINER_CLI: "podman",
DEPLOY_SHA: "1234567890abcdef",
...env,
},
);
expect(result.status).toBe(1);
expect(result.stderr).toContain(message);
expect(result.log).toBe("");
});
test("refuses stop-based deploys unless the container belongs to the expected unit", () => {
const result = runDeployScriptWithFakes(
{
curl: passingCurlCommand,
git: fakeGitCommand,
podman: fakePodmanCommand("other.service"),
},
{
DEPLOY_CONTAINER_CLI: "podman",
DEPLOY_EVENT_NAME: "push",
DEPLOY_REF: "refs/heads/main",
DEPLOY_RESTART_STRATEGY: "quadlet-container",
DEPLOY_SHA: "1234567890abcdef",
},
);
expect(result.status).toBe(1);
expect(result.stderr).toContain(
"refusing to stop dimensionlab-website; expected PODMAN_SYSTEMD_UNIT=dimensionlab-website.service",
);
expect(result.log).not.toContain("build ");
expect(result.log).not.toContain("stop dimensionlab-website");
});
test("checks the expected unit before auto falls back to stopping the container", () => {
const result = runDeployScriptWithFakes(
{
curl: passingCurlCommand,
git: fakeGitCommand,
podman: fakePodmanCommand("other.service"),
systemctl: fakeSystemctlCommand({ active: false, show: true }),
},
{
DEPLOY_CONTAINER_CLI: "podman",
DEPLOY_EVENT_NAME: "push",
DEPLOY_REF: "refs/heads/main",
DEPLOY_RESTART_STRATEGY: "auto",
DEPLOY_SHA: "1234567890abcdef",
},
);
expect(result.status).toBe(1);
expect(result.stderr).toContain(
"refusing to stop dimensionlab-website; expected PODMAN_SYSTEMD_UNIT=dimensionlab-website.service",
);
expect(result.log).not.toContain("stop dimensionlab-website");
});
});
type WorkspacePackageExport =
| string
| {
types?: string;
development?: string;
default?: string;
};
function expectPackageExport(
actual: WorkspacePackageExport | undefined,
expected: Exclude<WorkspacePackageExport, string>,
): void {
expect(actual).toMatchObject(expected);
}
function runDeployScriptWithFakes(
commands: Record<string, string>,
env: Record<string, string>,
): { log: string; status: number | null; stderr: string; stdout: string } {
const tempDir = mkdtempSync(join(tmpdir(), "dimensionlab-deploy-test-"));
const logPath = join(tempDir, "commands.log");
for (const [name, source] of Object.entries(commands)) {
const commandPath = join(tempDir, name);
writeFileSync(commandPath, source);
chmodSync(commandPath, 0o755);
}
const result = spawnSync("bash", [join(root, "scripts/deploy-dimensionlab-website.sh")], {
cwd: root,
encoding: "utf8",
env: {
...process.env,
...env,
DEPLOY_TEST_LOG: logPath,
PATH: `${tempDir}:${process.env.PATH ?? ""}`,
},
});
return {
log: existsSync(logPath) ? readFileSync(logPath, "utf8") : "",
status: result.status,
stderr: result.stderr,
stdout: result.stdout,
};
}
const fakeGitCommand = `#!/usr/bin/env bash
case "$1" in
branch)
echo main
;;
rev-parse)
echo 1234567890abcdef
;;
config|submodule)
exit 0
;;
esac
`;
function fakePodmanCommand(systemdUnit: string): string {
return `#!/usr/bin/env bash
state_file="$DEPLOY_TEST_LOG.state"
[ -f "$state_file" ] || printf 'initial' > "$state_file"
printf '%s\\n' "$*" >> "$DEPLOY_TEST_LOG"
if [ "$1" = "image" ] && [ "$2" = "inspect" ]; then
if [ "$4" = "--format" ]; then
case "$3" in
*:rollback-*)
echo sha256:old
;;
*)
if [ "$(cat "$state_file")" = "rollback" ]; then
echo sha256:old
else
echo sha256:new
fi
;;
esac
fi
exit 0
fi
if [ "$1" = "tag" ] && [ "$2" != "localhost/dimensionlab-website:latest" ]; then
printf 'rollback' > "$state_file"
fi
if [ "$1" = "inspect" ]; then
case "$*" in
*PODMAN_SYSTEMD_UNIT*)
echo ${systemdUnit}
;;
*State.Running*)
echo true
;;
*'.Image'*|*'{{.Image}}'*)
if [ "$(cat "$state_file")" = "rollback" ]; then
echo sha256:old
elif [ "\${DEPLOY_TEST_CONTAINER_IMAGE:-new}" = "wrong" ]; then
echo sha256:wrong
else
echo sha256:new
fi
;;
esac
fi
`;
}
const failingCurlCommand = `#!/usr/bin/env bash
printf 'curl %s\\n' "$*" >> "$DEPLOY_TEST_LOG"
exit 22
`;
const passingCurlCommand = `#!/usr/bin/env bash
printf 'curl %s\\n' "$*" >> "$DEPLOY_TEST_LOG"
if [ "$*" = *'/api/dashboard/tiles'* ]; then
printf '{"state":"ready","tiles":[]}'
fi
`;
function fakeSystemctlCommand(options: { active: boolean; show: boolean }): string {
const activeStatus = options.active ? 0 : 3;
const showStatus = options.show ? 0 : 1;
return `#!/usr/bin/env bash
printf 'systemctl %s\\n' "$*" >> "$DEPLOY_TEST_LOG"
if [ "$1" = "--user" ] && [ "$2" = "is-active" ]; then
exit ${activeStatus}
fi
if [ "$1" = "--user" ] && [ "$2" = "show" ]; then
exit ${showStatus}
fi
if [ "$1" = "--user" ] && [ "$2" = "restart" ]; then
exit 0
fi
`;
}

View file

@ -1,13 +0,0 @@
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import App from "./App";
import "./app.css";
const root = document.getElementById("root");
if (!root) throw new Error("Missing React root element");
createRoot(root).render(
<StrictMode>
<App />
</StrictMode>,
);

View file

@ -1,97 +0,0 @@
import { renderToString } from "react-dom/server";
import { describe, expect, test } from "vitest";
import { genericDashboardFixture } from "@dimensionlab/dashboard-model/fixtures";
import { AppStateView, resolveDocumentMetadata } 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("derives browser metadata from ready and fallback runtime states", () => {
const ready = resolveDocumentMetadata({
state: "ready",
document: genericDashboardFixture,
schemaVersion: "dashboard.v1",
currentRevisionId: "revision-1234567890",
});
const empty = resolveDocumentMetadata({
state: "empty",
title: "No Dashboard Model",
subtitle: "No active document",
message: "No validated dashboard document is active yet.",
});
expect(ready).toEqual({
title: "Operations Console",
description: "Generic environment",
});
expect(empty).toEqual({
title: "No Dashboard Model",
description: "No active document",
});
});
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");
});
});

View file

@ -1,33 +0,0 @@
import { readFileSync } from "node:fs";
import { join } from "node:path";
import { describe, expect, test } from "vitest";
import { createDevServerConfig } from "../../vite.config";
import { apiServerArgs } from "./dev";
describe("local development runtime", () => {
test("starts the Bun API server together with the Vite dev server", () => {
const packageJson = JSON.parse(
readFileSync(join(process.cwd(), "package.json"), "utf8"),
) as { scripts?: Record<string, string> };
expect(packageJson.scripts?.dev).toBe("bun src/server/dev.ts");
});
test("proxies dashboard API requests from Vite to the Bun API server", () => {
const server = createDevServerConfig({
DASHBOARD_DEV_API_TARGET: "http://127.0.0.1:5174",
});
expect(server?.proxy?.["/api"]).toMatchObject({
target: "http://127.0.0.1:5174",
changeOrigin: true,
});
});
test("uses development package export conditions for the Bun API server", () => {
expect(apiServerArgs).toEqual([
"--conditions=development",
"src/server/index.ts",
]);
});
});

View file

@ -1,76 +0,0 @@
const webHost = process.env.HOST || "0.0.0.0";
const webPort = process.env.PORT || "5173";
const apiHost = process.env.DASHBOARD_DEV_API_HOST || "127.0.0.1";
const apiPort = process.env.DASHBOARD_DEV_API_PORT || "5174";
const apiTarget = `http://${apiHost}:${apiPort}`;
export const apiServerArgs = [
"--conditions=development",
"src/server/index.ts",
] as const;
if (import.meta.main) {
runDevServers();
}
export function runDevServers(): void {
const children: Array<ReturnType<typeof Bun.spawn>> = [];
let shuttingDown = false;
function spawn(
label: string,
command: string[],
env: Record<string, string> = {},
): void {
const child = Bun.spawn(command, {
env: {
...process.env,
...env,
},
stdin: "inherit",
stdout: "inherit",
stderr: "inherit",
});
children.push(child);
void child.exited.then((code) => {
if (shuttingDown) return;
console.error(`${label} exited with status ${code}`);
shutdown(code || 1);
});
}
function shutdown(code = 0): void {
if (shuttingDown) return;
shuttingDown = true;
for (const child of children) {
child.kill();
}
void Promise.allSettled(children.map((child) => child.exited)).then(() => {
process.exit(code);
});
}
process.on("SIGINT", () => shutdown(0));
process.on("SIGTERM", () => shutdown(0));
spawn("api server", [process.execPath, ...apiServerArgs], {
HOST: apiHost,
PORT: apiPort,
});
spawn("vite dev server", [
process.execPath,
"x",
"vite",
"--host",
webHost,
"--port",
webPort,
], {
DASHBOARD_DEV_API_TARGET: apiTarget,
});
console.info(`Dashboard API proxy target: ${apiTarget}`);
}

View file

@ -1,77 +0,0 @@
import { afterAll, describe, expect, test, vi } from "vitest";
import { handleRequest } from "./index";
describe("server request routing", () => {
const consoleInfo = vi.spyOn(console, "info").mockImplementation(() => undefined);
afterAll(() => {
consoleInfo.mockRestore();
});
test("routes dashboard tile batch requests", async () => {
const response = await handleRequest(
new Request("https://example.test/api/dashboard/tiles", {
method: "POST",
body: JSON.stringify({
tiles: [
{
kind: "status",
stripId: "footer-status",
id: "auto-refresh",
},
],
}),
}),
);
expect(response.status).toBe(200);
await expect(response.json()).resolves.toMatchObject({
state: "ready",
tiles: [
{
state: "ready",
tile: {
kind: "status",
stripId: "footer-status",
id: "auto-refresh",
},
},
],
});
});
test("rejects non-post dashboard tile batch requests", async () => {
const response = await handleRequest(
new Request("https://example.test/api/dashboard/tiles", {
method: "GET",
}),
);
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");
});
});

View file

@ -1,104 +0,0 @@
import { extname, normalize } from "node:path";
import { handleAgentDashboardRoute } from "./routes/agent-dashboard";
import {
handleDashboardEventsRoute,
handleDashboardRoute,
handleDashboardTileRoute,
handleDashboardTilesRoute,
} from "./routes/dashboard";
const host = process.env.HOST || "0.0.0.0";
const port = Number(process.env.PORT || 3000);
const distRoot = `${process.cwd()}/dist`;
const contentTypes = new Map([
[".css", "text/css; charset=utf-8"],
[".html", "text/html; charset=utf-8"],
[".js", "text/javascript; charset=utf-8"],
[".json", "application/json; charset=utf-8"],
[".svg", "image/svg+xml"],
[".wasm", "application/wasm"],
]);
export async function handleRequest(request: Request): Promise<Response> {
const url = new URL(request.url);
if (url.pathname === "/api/dashboard") {
if (request.method !== "GET") return methodNotAllowed(["GET"]);
return handleDashboardRoute();
}
if (url.pathname === "/api/dashboard/tiles") {
if (request.method !== "POST") return methodNotAllowed(["POST"]);
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);
}
if (url.pathname === "/api/agent/dashboard") {
if (request.method !== "POST") return methodNotAllowed(["POST"]);
return handleAgentDashboardRoute(request);
}
if (url.pathname.startsWith("/api/")) {
return Response.json({ ok: false, message: "Not found" }, { status: 404 });
}
return serveStaticAsset(url.pathname);
}
async function serveStaticAsset(pathname: string): Promise<Response> {
const safePath = normalize(pathname).replace(/^(\.\.(\/|\\|$))+/, "");
const assetPath = safePath === "/" || safePath === "." ? "/index.html" : safePath;
const file = Bun.file(`${distRoot}${assetPath}`);
if (await file.exists()) {
return new Response(file, {
headers: contentTypeHeaders(assetPath),
});
}
const fallback = Bun.file(`${distRoot}/index.html`);
if (await fallback.exists()) {
return new Response(fallback, {
headers: contentTypeHeaders(".html"),
});
}
return new Response("Build output not found", { status: 404 });
}
function methodNotAllowed(allowedMethods: string[]): Response {
return Response.json(
{ ok: false, message: "Method not allowed" },
{
status: 405,
headers: {
Allow: allowedMethods.join(", "),
},
},
);
}
function contentTypeHeaders(pathname: string): HeadersInit {
const contentType = contentTypes.get(extname(pathname));
return contentType ? { "Content-Type": contentType } : {};
}
if (import.meta.main) {
Bun.serve({
hostname: host,
port,
fetch: handleRequest,
});
console.info(`Dimension Lab website listening on http://${host}:${port}`);
}

View file

@ -1,12 +0,0 @@
import { describe, expect, test } from "vitest";
import { handleAgentDashboardRoute } from "./agent-dashboard";
describe("agent dashboard API route", () => {
test("delegates unauthorized requests to the existing agent handler", async () => {
const response = await handleAgentDashboardRoute(
new Request("http://localhost/api/agent/dashboard", { method: "POST" }),
);
expect(response.status).toBe(401);
});
});

View file

@ -1,5 +0,0 @@
import { handleAgentDashboardRequest } from "$lib/server/agent-config";
export function handleAgentDashboardRoute(request: Request): Promise<Response> {
return handleAgentDashboardRequest(request);
}

View file

@ -1,799 +0,0 @@
import { afterAll, afterEach, describe, expect, test, vi } from "vitest";
import { dimensionLabDashboardFixture } from "$lib/dashboard-seed/dimensionlab";
import {
createDashboardTileCache,
dashboardTileCacheKey,
handleDashboardEventsRoute,
handleDashboardTilesRoute,
handleDashboardTileRoute,
loadDashboardResponse,
loadDashboardTilesResponse,
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"),
);
const response = await loadDashboardResponse({
refreshSeedDocument: true,
seedIfEmpty: true,
});
expect(response.state).toBe("ready");
if (response.state !== "ready") throw new Error("expected ready dashboard");
expect(response.document.metadata.title).toBe(
dimensionLabDashboardFixture.metadata.title,
);
expect(fetch).not.toHaveBeenCalled();
fetch.mockRestore();
});
test("reports when client-side live hydration is disabled", async () => {
const previous = process.env.DISABLE_LIVE_DATASOURCES;
process.env.DISABLE_LIVE_DATASOURCES = "1";
try {
const response = await loadDashboardResponse({
refreshSeedDocument: true,
seedIfEmpty: true,
});
expect(response.state).toBe("ready");
if (response.state !== "ready") throw new Error("expected ready dashboard");
expect(response.liveDatasourceHydration).toEqual({ enabled: false });
} finally {
if (previous === undefined) {
delete process.env.DISABLE_LIVE_DATASOURCES;
} else {
process.env.DISABLE_LIVE_DATASOURCES = previous;
}
}
});
test("hydrates a telemetry tile independently from the dashboard shell", async () => {
const fetch = vi.fn(async (input: RequestInfo | URL) => {
const url = String(input);
if (url.startsWith("https://prometheus.example/api/v1/query_range")) {
return jsonResponse({
status: "success",
data: {
result: [
{
metric: { host: "linux-infra" },
values: [
[1771430000, "10"],
[1771430060, "20"],
[1771430120, "42"],
],
},
],
},
});
}
if (url.startsWith("https://prometheus.example/api/v1/query")) {
return jsonResponse({
status: "success",
data: {
result: [
{
metric: { host: "linux-infra" },
value: [1771430400, "42"],
},
],
},
});
}
throw new Error(`Unhandled test request: ${url}`);
});
const response = await loadDashboardTileResponse(
{ kind: "telemetry", id: "infra-ram" },
{
fetch,
prometheusBaseUrl: "https://prometheus.example",
refreshSeedDocument: true,
seedIfEmpty: true,
},
);
expect(response.state).toBe("ready");
if (response.state !== "ready") throw new Error("expected ready tile");
expect(response.tile).toEqual({ kind: "telemetry", id: "infra-ram" });
expect(response.item).toMatchObject({
id: "infra-ram",
value: { kind: "percent", value: 42 },
severity: "ok",
detail: "linux-infra",
sparkline: [10, 20, 42],
});
expect(fetch).toHaveBeenCalledWith(
expect.stringContaining("/api/v1/query?"),
expect.objectContaining({ cache: "no-store" }),
);
expect(fetch).toHaveBeenCalledWith(
expect.stringContaining("/api/v1/query_range?"),
expect.objectContaining({ cache: "no-store" }),
);
});
test("hydrates a service tile with its service group identity", async () => {
const fetch = vi.fn(async () =>
jsonResponse({
status: "UP",
ping: 42,
}),
);
const response = await loadDashboardTileResponse(
{ kind: "service", groupId: "essentials", id: "vaultwarden" },
{
fetch,
refreshSeedDocument: true,
seedIfEmpty: true,
},
);
expect(response.state).toBe("ready");
if (response.state !== "ready") throw new Error("expected ready tile");
expect(response.tile).toEqual({
kind: "service",
groupId: "essentials",
id: "vaultwarden",
});
expect(response.item).toMatchObject({
id: "vaultwarden",
severity: "ok",
detail: "42 ms",
});
});
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({
status: "UP",
ping: 42,
});
});
const tile = { kind: "service", groupId: "essentials", id: "vaultwarden" } as const;
const first = await loadDashboardTileResponse(tile, {
fetch,
now: () => now,
refreshSeedDocument: true,
seedIfEmpty: true,
tileCache: cache,
});
const second = await loadDashboardTileResponse(tile, {
fetch,
now: () => now + 29_999,
refreshSeedDocument: true,
seedIfEmpty: true,
tileCache: cache,
});
now += 30_001;
const third = await loadDashboardTileResponse(tile, {
fetch,
now: () => now,
refreshSeedDocument: true,
seedIfEmpty: true,
tileCache: cache,
});
expect(first).toEqual(second);
expect(third.state).toBe("ready");
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;
const fetch = telemetryFetch();
const tile = { kind: "telemetry", id: "infra-ram" } as const;
await loadDashboardTileResponse(tile, {
fetch,
now: () => now,
prometheusBaseUrl: "https://prometheus.example",
refreshSeedDocument: true,
seedIfEmpty: true,
tileCache: cache,
});
await loadDashboardTileResponse(tile, {
fetch,
now: () => now + 14_999,
prometheusBaseUrl: "https://prometheus.example",
refreshSeedDocument: true,
seedIfEmpty: true,
tileCache: cache,
});
now += 15_001;
await loadDashboardTileResponse(tile, {
fetch,
now: () => now,
prometheusBaseUrl: "https://prometheus.example",
refreshSeedDocument: true,
seedIfEmpty: true,
tileCache: cache,
});
expect(fetch).toHaveBeenCalledTimes(4);
});
test("keeps weather module tiles cached for ten minutes", async () => {
const cache = createDashboardTileCache();
let now = 1_000;
const fetch = vi.fn(async () =>
jsonResponse({
current: {
apparent_temperature: 19,
temperature_2m: 20,
weather_code: 0,
wind_speed_10m: 11,
},
}),
);
const tile = { kind: "module", id: "weather-amsterdam" } as const;
await loadDashboardTileResponse(tile, {
fetch,
now: () => now,
refreshSeedDocument: true,
seedIfEmpty: true,
tileCache: cache,
});
await loadDashboardTileResponse(tile, {
fetch,
now: () => now + 599_999,
refreshSeedDocument: true,
seedIfEmpty: true,
tileCache: cache,
});
now += 600_001;
await loadDashboardTileResponse(tile, {
fetch,
now: () => now,
refreshSeedDocument: true,
seedIfEmpty: true,
tileCache: cache,
});
expect(fetch).toHaveBeenCalledTimes(2);
});
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<Response>((resolve) => {
resolveFetch = resolve;
})
);
const tile = { kind: "service", groupId: "essentials", id: "vaultwarden" } as const;
const first = loadDashboardTileResponse(tile, {
fetch,
logTileResolution: (event) => logs.push(event),
now: () => 1_000,
refreshSeedDocument: true,
seedIfEmpty: true,
tileCache: cache,
});
const second = loadDashboardTileResponse(tile, {
fetch,
logTileResolution: (event) => logs.push(event),
now: () => 1_000,
refreshSeedDocument: true,
seedIfEmpty: true,
tileCache: cache,
});
await Promise.resolve();
expect(fetch).toHaveBeenCalledTimes(1);
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<never>((_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", () => {
expect(
dashboardTileCacheKey({ kind: "service", groupId: "a:b", id: "c" }),
).not.toBe(
dashboardTileCacheKey({ kind: "service", groupId: "a", id: "b:c" }),
);
expect(
dashboardTileCacheKey({ kind: "status", stripId: "a:b", id: "c" }),
).not.toBe(
dashboardTileCacheKey({ kind: "status", stripId: "a", id: "b:c" }),
);
});
test("uses thirty-second status aggregate and five-minute static status ttl buckets", async () => {
const cache = createDashboardTileCache();
const fetch = vi.fn(async () => jsonResponse({ status: "UP", ping: 42 }));
const serviceCheckCount = dimensionLabDashboardFixture.serviceGroups
.flatMap((group) => group.services).length;
await loadDashboardTileResponse(
{ kind: "status", stripId: "footer-status", id: "system-status" },
{
fetch,
now: () => 1_000,
refreshSeedDocument: true,
seedIfEmpty: true,
tileCache: cache,
},
);
await loadDashboardTileResponse(
{ kind: "status", stripId: "footer-status", id: "system-status" },
{
fetch,
now: () => 31_001,
refreshSeedDocument: true,
seedIfEmpty: true,
tileCache: cache,
},
);
await loadDashboardTileResponse(
{ kind: "status", stripId: "footer-status", id: "auto-refresh" },
{
fetch,
now: () => 1_000,
refreshSeedDocument: true,
seedIfEmpty: true,
tileCache: cache,
},
);
await loadDashboardTileResponse(
{ kind: "status", stripId: "footer-status", id: "auto-refresh" },
{
fetch,
now: () => 300_999,
refreshSeedDocument: true,
seedIfEmpty: true,
tileCache: cache,
},
);
await loadDashboardTileResponse(
{ kind: "status", stripId: "footer-status", id: "auto-refresh" },
{
fetch,
now: () => 301_001,
refreshSeedDocument: true,
seedIfEmpty: true,
tileCache: cache,
},
);
expect(fetch).toHaveBeenCalledTimes(serviceCheckCount * 2);
});
test("serves tile route responses with short private cache headers", async () => {
const response = await handleDashboardTileRoute(
"/api/dashboard/tile/service/essentials/vaultwarden",
{
fetch: vi.fn(async () => jsonResponse({ status: "UP", ping: 42 })),
refreshSeedDocument: true,
seedIfEmpty: true,
tileCache: createDashboardTileCache(),
},
);
expect(response.headers.get("cache-control")).toBe(
"private, max-age=5, stale-while-revalidate=30",
);
});
test("serves batch tile route responses", async () => {
const response = await handleDashboardTilesRoute(
new Request("https://example.test/api/dashboard/tiles", {
method: "POST",
body: JSON.stringify({
tiles: [
{
kind: "status",
stripId: "footer-status",
id: "auto-refresh",
},
],
}),
}),
{
refreshSeedDocument: true,
seedIfEmpty: true,
tileCache: createDashboardTileCache(),
},
);
expect(response.status).toBe(200);
expect(response.headers.get("cache-control")).toBe(
"private, max-age=5, stale-while-revalidate=30",
);
await expect(response.json()).resolves.toMatchObject({
state: "ready",
tiles: [
{
state: "ready",
tile: {
kind: "status",
stripId: "footer-status",
id: "auto-refresh",
},
},
],
});
});
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", {
method: "POST",
body: JSON.stringify({
tiles: [{ kind: "service", id: "missing-group" }],
}),
}),
);
expect(response.status).toBe(400);
});
test("resolves batch tile responses with server concurrency capped at six", async () => {
let active = 0;
let maxActive = 0;
const started: string[] = [];
const releases = new Map<string, () => void>();
const service = dimensionLabDashboardFixture.serviceGroups
.flatMap((group) => group.services)[0];
if (!service) throw new Error("missing service fixture");
const tiles = Array.from({ length: 7 }, (_, index) => ({
kind: "service" as const,
groupId: "essentials",
id: `service-${index}`,
}));
const batch = loadDashboardTilesResponse(tiles, {
refreshSeedDocument: true,
seedIfEmpty: true,
tileCache: {
async resolve(key) {
active += 1;
maxActive = Math.max(maxActive, active);
started.push(key);
await new Promise<void>((resolve) => releases.set(key, resolve));
active -= 1;
return {
cache: "miss",
coalesced: false,
response: {
state: "ready",
tile: JSON.parse(key),
item: service,
},
};
},
},
});
await waitFor(() => started.length === 6);
expect(maxActive).toBe(6);
releases.get(started[0])?.();
await waitFor(() => started.length === 7);
for (const release of releases.values()) release();
await expect(batch).resolves.toMatchObject({
state: "ready",
tiles: expect.arrayContaining([
expect.objectContaining({
state: "ready",
tile: tiles[0],
}),
]),
});
expect(maxActive).toBe(6);
});
test("does not hydrate tile routes when live datasources are disabled", async () => {
const previous = process.env.DISABLE_LIVE_DATASOURCES;
process.env.DISABLE_LIVE_DATASOURCES = "1";
const fetch = vi.spyOn(globalThis, "fetch").mockRejectedValue(
new Error("live datasource fetch should not run when disabled"),
);
try {
const response = await loadDashboardTileResponse(
{ kind: "telemetry", id: "infra-ram" },
{
refreshSeedDocument: true,
seedIfEmpty: true,
},
);
expect(response.state).toBe("disabled");
expect(fetch).not.toHaveBeenCalled();
} finally {
fetch.mockRestore();
if (previous === undefined) {
delete process.env.DISABLE_LIVE_DATASOURCES;
} else {
process.env.DISABLE_LIVE_DATASOURCES = previous;
}
}
});
});
function jsonResponse(payload: unknown): Response {
return new Response(JSON.stringify(payload), {
headers: { "content-type": "application/json" },
});
}
async function waitFor(predicate: () => boolean) {
for (let attempt = 0; attempt < 20; attempt += 1) {
if (predicate()) return;
await Promise.resolve();
}
throw new Error("condition was not met");
}
function telemetryFetch() {
return vi.fn(async (input: RequestInfo | URL) => {
const url = String(input);
if (url.startsWith("https://prometheus.example/api/v1/query_range")) {
return jsonResponse({
status: "success",
data: {
result: [
{
metric: { host: "linux-infra" },
values: [
[1771430000, "10"],
[1771430060, "20"],
[1771430120, "42"],
],
},
],
},
});
}
if (url.startsWith("https://prometheus.example/api/v1/query")) {
return jsonResponse({
status: "success",
data: {
result: [
{
metric: { host: "linux-infra" },
value: [1771430400, "42"],
},
],
},
});
}
throw new Error(`Unhandled test request: ${url}`);
});
}

View file

@ -1,609 +0,0 @@
import type { DashboardDocument } from "@dimensionlab/dashboard-model";
import {
loadDashboardRuntime,
type DashboardRuntimeOptions,
type DashboardRuntimeState,
} from "$lib/server/dashboard";
import {
resolveDashboardDatasources,
resolveDashboardTile,
type DashboardTileReference,
type DashboardTileResolution,
type DatasourceResolutionOptions,
} from "$lib/server/datasources";
export interface LoadDashboardResponseOptions
extends Pick<
DashboardRuntimeOptions,
"refreshSeedDocument" | "seedIfEmpty" | "seedDocument"
>,
DatasourceResolutionOptions {
disableLiveDatasources?: boolean;
hydrateLiveDatasources?: boolean;
logTileResolution?: (event: DashboardTileResolutionLogEvent) => void;
now?: () => number;
tileCache?: DashboardTileCache;
}
export interface DashboardEventsRouteOptions extends LoadDashboardResponseOptions {
signal?: AbortSignal;
}
interface DashboardTileCacheEntry {
expiresAt: number;
response: DashboardTileResolution;
}
export interface DashboardTileCache {
resolve(
key: string,
ttlMs: number,
now: number,
load: () => Promise<DashboardTileResolution>,
): Promise<DashboardTileCacheResult>;
}
const dashboardTileResponseHeaders = {
"Cache-Control": "private, max-age=5, stale-while-revalidate=30",
};
const dashboardBatchTileConcurrency = 6;
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 interface DashboardTilesBatchResponse {
state: "ready";
tiles: DashboardTileResolution[];
}
export function createDashboardTileCache(): DashboardTileCache {
const entries = new Map<string, DashboardTileCacheEntry>();
const inFlight = new Map<string, Promise<DashboardTileResolution>>();
return {
async resolve(key, ttlMs, now, load) {
const cached = entries.get(key);
if (cached && cached.expiresAt > now) {
return {
cache: "hit",
coalesced: false,
response: 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,
});
});
}
const request = load()
.then((response) => {
if (response.state === "ready") {
entries.set(key, {
expiresAt: now + ttlMs,
response,
});
}
return response;
})
.finally(() => {
inFlight.delete(key);
});
inFlight.set(key, request);
return request.then((response) => ({
cache: "miss" as const,
coalesced: false,
response,
}));
},
};
}
export async function loadDashboardResponse(
options: LoadDashboardResponseOptions = {},
): Promise<DashboardRuntimeState> {
const liveHydrationEnabled =
!options.disableLiveDatasources &&
process.env.DISABLE_LIVE_DATASOURCES !== "1";
const dashboard = loadDashboardRuntime(undefined, {
refreshSeedDocument: options.refreshSeedDocument ?? true,
seedIfEmpty: options.seedIfEmpty ?? true,
seedDocument: options.seedDocument,
});
if (dashboard.state !== "ready") {
return dashboard;
}
if (
!options.hydrateLiveDatasources ||
options.disableLiveDatasources ||
process.env.DISABLE_LIVE_DATASOURCES === "1"
) {
return {
...dashboard,
liveDatasourceHydration: {
enabled: liveHydrationEnabled,
},
};
}
return {
...dashboard,
document: await resolveDashboardDatasources(dashboard.document, options),
liveDatasourceHydration: {
enabled: false,
},
};
}
export async function loadDashboardTileResponse(
tile: DashboardTileReference,
options: LoadDashboardResponseOptions = {},
): Promise<DashboardTileResolution> {
if (
options.disableLiveDatasources ||
process.env.DISABLE_LIVE_DATASOURCES === "1"
) {
const tileKey = dashboardTileCacheKey(tile);
const startedAt = options.now?.() ?? Date.now();
const response = {
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,
seedDocument: options.seedDocument,
});
if (dashboard.state !== "ready") {
const response = {
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;
}
}
export async function loadDashboardTilesResponse(
tiles: DashboardTileReference[],
options: LoadDashboardResponseOptions = {},
): Promise<DashboardTilesBatchResponse> {
return {
state: "ready",
tiles: await resolveDashboardTilesBatch(tiles, options),
};
}
export async function handleDashboardRoute(): Promise<Response> {
return Response.json(await loadDashboardResponse());
}
export async function handleDashboardTileRoute(
pathname: string,
options: LoadDashboardResponseOptions = {},
): Promise<Response> {
const tile = parseDashboardTilePath(pathname);
if (!tile) {
return Response.json(
{ ok: false, message: "Invalid dashboard tile route" },
{ status: 404 },
);
}
const response = await loadDashboardTileResponse(tile, options);
return Response.json(response, {
status: response.state === "not_found" ? 404 : 200,
headers: dashboardTileResponseHeaders,
});
}
export async function handleDashboardTilesRoute(
request: Request,
options: LoadDashboardResponseOptions = {},
): Promise<Response> {
const tiles = await parseDashboardTilesBatchRequest(request);
if (!tiles) {
return Response.json(
{ ok: false, message: "Invalid dashboard tiles request" },
{ status: 400 },
);
}
return Response.json(await loadDashboardTilesResponse(tiles, options), {
headers: dashboardTileResponseHeaders,
});
}
export async function handleDashboardEventsRoute(
options: DashboardEventsRouteOptions = {},
): Promise<Response> {
const stream = new ReadableStream<Uint8Array>({
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);
}
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";
}
async function resolveDashboardTilesBatch(
tiles: DashboardTileReference[],
options: LoadDashboardResponseOptions,
): Promise<DashboardTileResolution[]> {
const results: DashboardTileResolution[] = new Array(tiles.length);
let nextIndex = 0;
async function worker() {
while (nextIndex < tiles.length) {
const index = nextIndex;
nextIndex += 1;
results[index] = await loadDashboardTileResponse(tiles[index], options);
}
}
await Promise.all(
Array.from(
{ length: Math.min(dashboardBatchTileConcurrency, tiles.length) },
() => worker(),
),
);
return results;
}
async function parseDashboardTilesBatchRequest(
request: Request,
): Promise<DashboardTileReference[] | null> {
try {
const body = await request.json() as unknown;
if (
typeof body !== "object" ||
body === null ||
!("tiles" in body) ||
!Array.isArray(body.tiles)
) {
return null;
}
const tiles = body.tiles.map(parseDashboardTileReference);
return tiles.every((tile): tile is DashboardTileReference => tile !== null)
? tiles
: null;
} catch {
return null;
}
}
function parseDashboardTileReference(value: unknown): DashboardTileReference | null {
if (typeof value !== "object" || value === null || !("kind" in value)) {
return null;
}
if (
(value.kind === "telemetry" || value.kind === "module") &&
"id" in value &&
typeof value.id === "string"
) {
return { kind: value.kind, id: value.id };
}
if (
value.kind === "service" &&
"groupId" in value &&
typeof value.groupId === "string" &&
"id" in value &&
typeof value.id === "string"
) {
return {
kind: "service",
groupId: value.groupId,
id: value.id,
};
}
if (
value.kind === "status" &&
"stripId" in value &&
typeof value.stripId === "string" &&
"id" in value &&
typeof value.id === "string"
) {
return {
kind: "status",
stripId: value.stripId,
id: value.id,
};
}
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;
override readonly cause: unknown;
constructor(
cause: unknown,
metadata: Pick<DashboardTileCacheResult, "cache" | "coalesced">,
) {
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,
): number {
if (tile.kind === "telemetry") return 15_000;
if (tile.kind === "service") return 30_000;
if (tile.kind === "module") {
const module = document.modules?.find((item) => item.id === tile.id);
if (
module?.datasource?.type === "external" &&
module.datasource.adapter === "weather"
) {
return 10 * 60_000;
}
return 30_000;
}
if (["system-status", "uptime", "load-avg"].includes(tile.id)) {
return 30_000;
}
return 5 * 60_000;
}
function parseDashboardTilePath(pathname: string): DashboardTileReference | null {
const parts = pathname.split("/").filter(Boolean);
const [, dashboard, tileRoot, kind, firstId, secondId] = parts;
if (dashboard !== "dashboard" || tileRoot !== "tile" || !kind || !firstId) {
return null;
}
const id = decodeURIComponent(firstId);
if (kind === "telemetry" || kind === "module") {
return { kind, id };
}
if (kind === "service" && secondId) {
return {
kind,
groupId: id,
id: decodeURIComponent(secondId),
};
}
if (kind === "status" && secondId) {
return {
kind,
stripId: id,
id: decodeURIComponent(secondId),
};
}
return null;
}

View file

@ -1,3 +0,0 @@
/// <reference types="vite/client" />
declare module "*.css" {}

View file

@ -1,349 +0,0 @@
/* eslint-disable */
/* tslint:disable */
/**
* Mock Service Worker.
* @see https://github.com/mswjs/msw
* - Please do NOT modify this file.
*/
const PACKAGE_VERSION = '2.14.6'
const INTEGRITY_CHECKSUM = '4db4a41e972cec1b64cc569c66952d82'
const IS_MOCKED_RESPONSE = Symbol('isMockedResponse')
const activeClientIds = new Set()
addEventListener('install', function () {
self.skipWaiting()
})
addEventListener('activate', function (event) {
event.waitUntil(self.clients.claim())
})
addEventListener('message', async function (event) {
const clientId = Reflect.get(event.source || {}, 'id')
if (!clientId || !self.clients) {
return
}
const client = await self.clients.get(clientId)
if (!client) {
return
}
const allClients = await self.clients.matchAll({
type: 'window',
})
switch (event.data) {
case 'KEEPALIVE_REQUEST': {
sendToClient(client, {
type: 'KEEPALIVE_RESPONSE',
})
break
}
case 'INTEGRITY_CHECK_REQUEST': {
sendToClient(client, {
type: 'INTEGRITY_CHECK_RESPONSE',
payload: {
packageVersion: PACKAGE_VERSION,
checksum: INTEGRITY_CHECKSUM,
},
})
break
}
case 'MOCK_ACTIVATE': {
activeClientIds.add(clientId)
sendToClient(client, {
type: 'MOCKING_ENABLED',
payload: {
client: {
id: client.id,
frameType: client.frameType,
},
},
})
break
}
case 'CLIENT_CLOSED': {
activeClientIds.delete(clientId)
const remainingClients = allClients.filter((client) => {
return client.id !== clientId
})
// Unregister itself when there are no more clients
if (remainingClients.length === 0) {
self.registration.unregister()
}
break
}
}
})
addEventListener('fetch', function (event) {
const requestInterceptedAt = Date.now()
// Bypass navigation requests.
if (event.request.mode === 'navigate') {
return
}
// Opening the DevTools triggers the "only-if-cached" request
// that cannot be handled by the worker. Bypass such requests.
if (
event.request.cache === 'only-if-cached' &&
event.request.mode !== 'same-origin'
) {
return
}
// Bypass all requests when there are no active clients.
// Prevents the self-unregistered worked from handling requests
// after it's been terminated (still remains active until the next reload).
if (activeClientIds.size === 0) {
return
}
const requestId = crypto.randomUUID()
event.respondWith(handleRequest(event, requestId, requestInterceptedAt))
})
/**
* @param {FetchEvent} event
* @param {string} requestId
* @param {number} requestInterceptedAt
*/
async function handleRequest(event, requestId, requestInterceptedAt) {
const client = await resolveMainClient(event)
const requestCloneForEvents = event.request.clone()
const response = await getResponse(
event,
client,
requestId,
requestInterceptedAt,
)
// Send back the response clone for the "response:*" life-cycle events.
// Ensure MSW is active and ready to handle the message, otherwise
// this message will pend indefinitely.
if (client && activeClientIds.has(client.id)) {
const serializedRequest = await serializeRequest(requestCloneForEvents)
// Clone the response so both the client and the library could consume it.
const responseClone = response.clone()
sendToClient(
client,
{
type: 'RESPONSE',
payload: {
isMockedResponse: IS_MOCKED_RESPONSE in response,
request: {
id: requestId,
...serializedRequest,
},
response: {
type: responseClone.type,
status: responseClone.status,
statusText: responseClone.statusText,
headers: Object.fromEntries(responseClone.headers.entries()),
body: responseClone.body,
},
},
},
responseClone.body ? [serializedRequest.body, responseClone.body] : [],
)
}
return response
}
/**
* Resolve the main client for the given event.
* Client that issues a request doesn't necessarily equal the client
* that registered the worker. It's with the latter the worker should
* communicate with during the response resolving phase.
* @param {FetchEvent} event
* @returns {Promise<Client | undefined>}
*/
async function resolveMainClient(event) {
const client = await self.clients.get(event.clientId)
if (activeClientIds.has(event.clientId)) {
return client
}
if (client?.frameType === 'top-level') {
return client
}
const allClients = await self.clients.matchAll({
type: 'window',
})
return allClients
.filter((client) => {
// Get only those clients that are currently visible.
return client.visibilityState === 'visible'
})
.find((client) => {
// Find the client ID that's recorded in the
// set of clients that have registered the worker.
return activeClientIds.has(client.id)
})
}
/**
* @param {FetchEvent} event
* @param {Client | undefined} client
* @param {string} requestId
* @param {number} requestInterceptedAt
* @returns {Promise<Response>}
*/
async function getResponse(event, client, requestId, requestInterceptedAt) {
// Clone the request because it might've been already used
// (i.e. its body has been read and sent to the client).
const requestClone = event.request.clone()
function passthrough() {
// Cast the request headers to a new Headers instance
// so the headers can be manipulated with.
const headers = new Headers(requestClone.headers)
// Remove the "accept" header value that marked this request as passthrough.
// This prevents request alteration and also keeps it compliant with the
// user-defined CORS policies.
const acceptHeader = headers.get('accept')
if (acceptHeader) {
const values = acceptHeader.split(',').map((value) => value.trim())
const filteredValues = values.filter(
(value) => value !== 'msw/passthrough',
)
if (filteredValues.length > 0) {
headers.set('accept', filteredValues.join(', '))
} else {
headers.delete('accept')
}
}
return fetch(requestClone, { headers })
}
// Bypass mocking when the client is not active.
if (!client) {
return passthrough()
}
// Bypass initial page load requests (i.e. static assets).
// The absence of the immediate/parent client in the map of the active clients
// means that MSW hasn't dispatched the "MOCK_ACTIVATE" event yet
// and is not ready to handle requests.
if (!activeClientIds.has(client.id)) {
return passthrough()
}
// Notify the client that a request has been intercepted.
const serializedRequest = await serializeRequest(event.request)
const clientMessage = await sendToClient(
client,
{
type: 'REQUEST',
payload: {
id: requestId,
interceptedAt: requestInterceptedAt,
...serializedRequest,
},
},
[serializedRequest.body],
)
switch (clientMessage.type) {
case 'MOCK_RESPONSE': {
return respondWithMock(clientMessage.data)
}
case 'PASSTHROUGH': {
return passthrough()
}
}
return passthrough()
}
/**
* @param {Client} client
* @param {any} message
* @param {Array<Transferable>} transferrables
* @returns {Promise<any>}
*/
function sendToClient(client, message, transferrables = []) {
return new Promise((resolve, reject) => {
const channel = new MessageChannel()
channel.port1.onmessage = (event) => {
if (event.data && event.data.error) {
return reject(event.data.error)
}
resolve(event.data)
}
client.postMessage(message, [
channel.port2,
...transferrables.filter(Boolean),
])
})
}
/**
* @param {Response} response
* @returns {Response}
*/
function respondWithMock(response) {
// Setting response status code to 0 is a no-op.
// However, when responding with a "Response.error()", the produced Response
// instance will have status code set to 0. Since it's not possible to create
// a Response instance with status code 0, handle that use-case separately.
if (response.status === 0) {
return Response.error()
}
const mockedResponse = new Response(response.body, response)
Reflect.defineProperty(mockedResponse, IS_MOCKED_RESPONSE, {
value: true,
enumerable: true,
})
return mockedResponse
}
/**
* @param {Request} request
*/
async function serializeRequest(request) {
return {
url: request.url,
mode: request.mode,
method: request.method,
headers: Object.fromEntries(request.headers.entries()),
cache: request.cache,
credentials: request.credentials,
destination: request.destination,
integrity: request.integrity,
redirect: request.redirect,
referrer: request.referrer,
referrerPolicy: request.referrerPolicy,
body: await request.arrayBuffer(),
keepalive: request.keepalive,
}
}

View file

@ -1,381 +0,0 @@
import AxeBuilder from "@axe-core/playwright";
import { expect, test, type Page } from "@playwright/test";
const linkedServiceIds = [
"vaultwarden",
"forgejo",
"wiki",
"aws-start",
"adguard-primary",
"adguard-secondary",
"grafana",
"uptime-kuma",
"prometheus",
"backrest",
"n8n",
"open-webui",
"comfyui",
"models",
"adminer",
"assistant",
"suna",
"cockpit-infra",
"cockpit-gpu",
"cockpit-network-core",
"forgejo-ssh-relay",
];
test.describe("dashboard page QA gate", () => {
test("renders the model-driven dashboard on desktop", async ({
page,
}, testInfo) => {
test.skip(testInfo.project.name !== "chromium-desktop");
await page.goto("/");
await waitForDashboardReady(page);
await expect(page.getByRole("main")).toHaveAttribute(
"aria-labelledby",
/title/,
);
await expect(
page.getByRole("heading", { level: 1, name: "System Overview" }),
).toBeVisible();
await expect(page.getByText("Infra RAM")).toBeVisible();
await expect(page.getByRole("link", { name: /Vaultwarden/i })).toBeVisible();
await expect(page.locator("[data-model-id='vaultwarden']")).toBeVisible();
await expect(page.locator(".service-row .icon-glyph svg").first()).toBeVisible();
const bodyBox = await page.locator("body").boundingBox();
expect(bodyBox?.width).toBeGreaterThan(1000);
await expect(page).toHaveScreenshot("dashboard-desktop.png", {
fullPage: true,
});
});
test("fits the operational dashboard into a 1470 by 956 viewport", async ({
page,
}, testInfo) => {
test.skip(testInfo.project.name !== "chromium-desktop");
await page.setViewportSize({ width: 1470, height: 956 });
await page.goto("/");
await waitForDashboardReady(page);
const metrics = await page.evaluate(() => {
const footer = document.querySelector("[data-model-id='footer-status']");
const runtime = document.querySelector("[data-model-id='runtime-health']");
const visibleItems = [
...document.querySelectorAll(".telemetry-card"),
...document.querySelectorAll(".service-row"),
...document.querySelectorAll(".footer-cell"),
].map((element) => {
const rect = element.getBoundingClientRect();
return {
bottom: rect.bottom,
height: rect.height,
id: element.getAttribute("data-model-id"),
top: rect.top,
width: rect.width,
};
});
return {
clippedItems: visibleItems.filter((item) =>
item.top < 0 ||
item.bottom > window.innerHeight ||
item.width <= 0 ||
item.height <= 0
),
footerCellCount: document.querySelectorAll(".footer-cell").length,
scrollWidth: document.documentElement.scrollWidth,
scrollHeight: document.documentElement.scrollHeight,
serviceRowCount: document.querySelectorAll(".service-row").length,
telemetryCardCount: document.querySelectorAll(".telemetry-card").length,
footerBottom: footer?.getBoundingClientRect().bottom ?? 0,
runtimeBottom: runtime?.getBoundingClientRect().bottom ?? 0,
};
});
expect(metrics.scrollWidth).toBeLessThanOrEqual(1470);
expect(metrics.scrollHeight).toBeLessThanOrEqual(956);
expect(metrics.runtimeBottom).toBeLessThanOrEqual(956);
expect(metrics.footerBottom).toBeLessThanOrEqual(956);
expect(metrics.telemetryCardCount).toBe(16);
expect(metrics.serviceRowCount).toBe(27);
expect(metrics.footerCellCount).toBe(5);
expect(metrics.clippedItems).toEqual([]);
});
test("keeps intermediate viewports scrollable without horizontal clipping", async ({
page,
}, testInfo) => {
test.skip(testInfo.project.name !== "chromium-desktop");
for (const viewport of [
{ width: 900, height: 956 },
{ width: 1024, height: 768 },
]) {
await page.setViewportSize(viewport);
await page.goto("/");
await waitForDashboardReady(page);
const metrics = await page.evaluate(() => {
const footer = document.querySelector("[data-model-id='footer-status']");
const trackedElements = [
document.querySelector(".dashboard-frame__header"),
...document.querySelectorAll(".telemetry-card"),
...document.querySelectorAll(".service-panel"),
document.querySelector("[data-model-id='runtime-health']"),
footer,
].filter((element): element is Element => Boolean(element));
const footerRect = footer?.getBoundingClientRect();
const clippedRight = trackedElements
.map((element) => {
const rect = element.getBoundingClientRect();
return {
id: element.getAttribute("data-model-id") || element.className,
right: rect.right,
width: rect.width,
};
})
.filter((item) => item.right > window.innerWidth + 1 || item.width <= 0);
return {
clippedRight,
footerBottomInDocument: (footerRect?.bottom ?? 0) + window.scrollY,
frameOverflow: window.getComputedStyle(
document.querySelector(".dashboard-frame") as Element,
).overflow,
scrollHeight: document.documentElement.scrollHeight,
scrollWidth: document.documentElement.scrollWidth,
};
});
expect(metrics.scrollWidth).toBeLessThanOrEqual(viewport.width);
expect(metrics.scrollHeight).toBeGreaterThanOrEqual(
Math.ceil(metrics.footerBottomInDocument),
);
expect(metrics.frameOverflow).not.toBe("hidden");
expect(metrics.clippedRight).toEqual([]);
}
});
test("renders bookmark rows as a compact divider list", async ({ page }, testInfo) => {
test.skip(testInfo.project.name !== "chromium-desktop");
await page.goto("/");
await waitForDashboardReady(page);
const bookmarkDensity = await page.evaluate(() => {
const panelBody = document.querySelector(".service-panel .panel__body");
const rows = Array.from(document.querySelectorAll<HTMLElement>(".service-panel .service-row"));
const first = rows[0];
const second = rows[1];
if (!panelBody || !first || !second) {
throw new Error("expected at least two bookmark rows");
}
const bodyStyle = window.getComputedStyle(panelBody);
const rowStyle = window.getComputedStyle(first);
const firstRect = first.getBoundingClientRect();
const secondRect = second.getBoundingClientRect();
return {
backgroundColor: rowStyle.backgroundColor,
borderBottomWidth: Number.parseFloat(rowStyle.borderBottomWidth),
borderLeftWidth: Number.parseFloat(rowStyle.borderLeftWidth),
borderRightWidth: Number.parseFloat(rowStyle.borderRightWidth),
columnGap: Number.parseFloat(rowStyle.columnGap),
paddingBottom: Number.parseFloat(rowStyle.paddingBottom),
paddingTop: Number.parseFloat(rowStyle.paddingTop),
panelGap: Number.parseFloat(bodyStyle.rowGap),
rowGap: secondRect.top - firstRect.bottom,
};
});
expect(bookmarkDensity.panelGap).toBe(0);
expect(bookmarkDensity.rowGap).toBeLessThanOrEqual(1);
expect(bookmarkDensity.paddingTop).toBeLessThanOrEqual(3);
expect(bookmarkDensity.paddingBottom).toBeLessThanOrEqual(3);
expect(bookmarkDensity.columnGap).toBeLessThanOrEqual(4);
expect(bookmarkDensity.borderBottomWidth).toBeGreaterThanOrEqual(1);
expect(bookmarkDensity.borderLeftWidth).toBe(0);
expect(bookmarkDensity.borderRightWidth).toBe(0);
expect(bookmarkDensity.backgroundColor).toBe("rgba(0, 0, 0, 0)");
});
test("balances dashboard typography at the target viewport", async ({ page }, testInfo) => {
test.skip(testInfo.project.name !== "chromium-desktop");
await page.setViewportSize({ width: 1470, height: 956 });
await page.goto("/");
await waitForDashboardReady(page);
const typeScale = await page.evaluate(() => {
const fontSize = (selector: string) => {
const element = document.querySelector(selector);
if (!element) throw new Error(`missing ${selector}`);
return Number.parseFloat(window.getComputedStyle(element).fontSize);
};
return {
h1: fontSize("h1"),
panelTitle: fontSize(".service-panel h2"),
serviceDescription: fontSize(".service-row p"),
serviceLabel: fontSize(".service-row h3"),
telemetryLabel: fontSize(".telemetry-card h3"),
telemetryValue: fontSize(".telemetry-card strong"),
};
});
expect(typeScale.serviceLabel).toBeGreaterThanOrEqual(11.4);
expect(typeScale.serviceDescription).toBeGreaterThanOrEqual(8.8);
expect(typeScale.h1).toBeLessThanOrEqual(49);
expect(typeScale.panelTitle).toBeLessThanOrEqual(28);
expect(typeScale.telemetryLabel).toBeLessThanOrEqual(9.5);
expect(typeScale.telemetryValue).toBeLessThanOrEqual(36);
});
test("keeps the first screen usable on mobile", async ({ page }, testInfo) => {
test.skip(testInfo.project.name !== "chromium-mobile");
await page.goto("/");
await waitForDashboardReady(page);
await expect(
page.getByRole("heading", { level: 1, name: "System Overview" }),
).toBeVisible();
await expect(page.getByLabel("Service groups")).toBeVisible();
await expect(page.locator(".service-row .icon-glyph svg").first()).toBeVisible();
await expect(page).toHaveScreenshot("dashboard-mobile.png", {
fullPage: true,
});
});
test("exposes usable landmarks and a visible keyboard focus state", async ({
page,
}) => {
await page.goto("/");
await waitForDashboardReady(page);
await expect(page.getByRole("main")).toHaveCount(1);
await page.keyboard.press("Tab");
const themeToggle = page.getByRole("button", { name: "Light theme" });
await expect(themeToggle).toBeFocused();
await expect(themeToggle).toHaveAttribute("aria-pressed", "false");
const themeFocusBoxShadow = await themeToggle.evaluate((element) => {
return window.getComputedStyle(element).boxShadow;
});
expect(themeFocusBoxShadow).not.toBe("none");
for (const serviceId of linkedServiceIds) {
await page.keyboard.press("Tab");
const focused = page.locator(":focus");
await expect(focused).toHaveAttribute("data-model-id", serviceId);
const focusBoxShadow = await focused.evaluate((element) => {
return window.getComputedStyle(element).boxShadow;
});
expect(focusBoxShadow).not.toBe("none");
}
});
test("passes automated accessibility checks", async ({ page }) => {
await page.goto("/");
await waitForDashboardReady(page);
const results = await new AxeBuilder({ page }).analyze();
expect(results.violations).toEqual([]);
});
test("passes automated accessibility checks in light mode", async ({
page,
}, testInfo) => {
test.skip(testInfo.project.name !== "chromium-desktop");
await page.goto("/");
await waitForDashboardReady(page);
await page.getByRole("button", { name: "Light theme" }).click();
await expect(page.locator("html")).toHaveAttribute("data-ui-theme", "light");
await expect(page.getByRole("button", { name: "Light theme" })).toHaveAttribute(
"aria-pressed",
"true",
);
const results = await new AxeBuilder({ page }).analyze();
expect(results.violations).toEqual([]);
await expect(page).toHaveScreenshot("dashboard-light-desktop.png", {
fullPage: true,
});
});
test("honors reduced-motion preferences", async ({ page }) => {
await page.emulateMedia({ reducedMotion: "reduce" });
await page.goto("/");
await waitForDashboardReady(page);
const durations = await page.evaluate(() => {
const element = document.createElement("div");
element.style.animation = "qa-motion-check 10s infinite";
element.style.transition = "opacity 10s linear";
document.body.append(element);
const styles = window.getComputedStyle(element);
return {
animation: styles.animationDuration,
transition: styles.transitionDuration,
};
});
expect(cssDurationToMilliseconds(durations.animation)).toBeLessThanOrEqual(
0.01,
);
expect(cssDurationToMilliseconds(durations.transition)).toBeLessThanOrEqual(
0.01,
);
});
test("toggles the dashboard between dark and light themes", async ({ page }) => {
await page.goto("/");
await waitForDashboardReady(page);
await expect(page.locator("html")).toHaveAttribute("data-ui-theme", "dark");
const switchToLight = page.getByRole("button", {
name: "Light theme",
});
await expect(switchToLight).toBeVisible();
await expect(switchToLight).toHaveAttribute("aria-pressed", "false");
await switchToLight.click();
await expect(page.locator("html")).toHaveAttribute("data-ui-theme", "light");
await expect(page.getByRole("button", { name: "Light theme" })).toHaveAttribute(
"aria-pressed",
"true",
);
await page.reload();
await waitForDashboardReady(page);
await expect(page.locator("html")).toHaveAttribute("data-ui-theme", "light");
});
});
async function waitForDashboardReady(page: Page): Promise<void> {
await expect(
page.getByRole("heading", { level: 1, name: "System Overview" }),
).toBeVisible();
await expect(page.locator("[data-model-id='vaultwarden']")).toBeVisible();
}
function cssDurationToMilliseconds(duration: string): number {
if (duration.endsWith("ms")) return Number.parseFloat(duration);
if (duration.endsWith("s")) return Number.parseFloat(duration) * 1000;
return Number.NaN;
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 301 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 451 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 302 KiB

View file

@ -1,35 +0,0 @@
import { existsSync } from "node:fs";
import { resolve, sep } from "node:path";
const root = resolve(process.cwd(), "packages/ui/storybook-static");
const port = Number(process.env.STORYBOOK_STATIC_PORT || 6007);
if (!existsSync(resolve(root, "iframe.html"))) {
throw new Error(
"packages/ui/storybook-static is missing. Run bun run build-storybook first.",
);
}
Bun.serve({
hostname: "127.0.0.1",
port,
async fetch(request) {
const url = new URL(request.url);
const pathname = decodeURIComponent(url.pathname);
const relativePath = pathname === "/" ? "/index.html" : pathname;
const filePath = resolve(root, `.${relativePath}`);
if (!filePath.startsWith(`${root}${sep}`)) {
return new Response("Forbidden", { status: 403 });
}
const file = Bun.file(filePath);
if (!(await file.exists())) {
return new Response("Not found", { status: 404 });
}
return new Response(file);
},
});
console.log(`Storybook static listening on http://127.0.0.1:${port}`);

View file

@ -1,40 +0,0 @@
import { expect, test } from "@playwright/test";
const storybookPort = Number(process.env.PLAYWRIGHT_STORYBOOK_PORT || 6007);
const storybookURL = `http://127.0.0.1:${storybookPort}`;
test.describe("Storybook theme QA", () => {
test("renders the ThemeToggle light story on the light canvas", async ({
page,
}, testInfo) => {
test.skip(testInfo.project.name !== "chromium-desktop");
const consoleMessages: string[] = [];
page.on("console", (message) => {
if (["error", "warning"].includes(message.type())) {
consoleMessages.push(message.text());
}
});
const url = `${storybookURL}/iframe.html?id=ui-themetoggle--light&viewMode=story`;
await page.goto(url, { waitUntil: "networkidle" });
await expect(page.locator("html")).toHaveAttribute("data-ui-theme", "light");
await expect(
page.getByRole("button", { name: "Light theme" }),
).toBeVisible();
await expect(page.getByRole("button", { name: "Light theme" })).toHaveAttribute(
"aria-pressed",
"true",
);
await expect(page.locator("body")).toHaveCSS(
"background-color",
"rgb(238, 242, 231)",
);
expect(
consoleMessages.filter((message) =>
message.includes("Global args/argTypes can only be set globally"),
),
).toEqual([]);
});
});

View file

@ -1,19 +0,0 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"baseUrl": ".",
"paths": {
"$lib/*": ["src/lib/*"]
},
"types": ["node", "bun-types", "react", "react-dom", "vite/client"]
},
"include": [
"src/**/*.ts",
"src/**/*.tsx",
"tests/**/*.ts",
"vite.config.ts",
"playwright.config.ts",
"drizzle.config.ts"
],
"exclude": ["build", "dist", "node_modules"]
}

View file

@ -1,34 +0,0 @@
import { fileURLToPath } from "node:url";
import tailwindcss from "@tailwindcss/vite";
import react from "@vitejs/plugin-react";
import { defineConfig, configDefaults } from "vitest/config";
import type { UserConfig } from "vite";
export function createDevServerConfig(
env: NodeJS.ProcessEnv = process.env,
): UserConfig["server"] {
const apiTarget = env.DASHBOARD_DEV_API_TARGET;
if (!apiTarget) return undefined;
return {
proxy: {
"/api": {
target: apiTarget,
changeOrigin: true,
},
},
};
}
export default defineConfig({
plugins: [react(), tailwindcss()],
resolve: {
alias: {
$lib: fileURLToPath(new URL("./src/lib", import.meta.url)),
},
},
server: createDevServerConfig(),
test: {
exclude: [...configDefaults.exclude, "tests/e2e/**"],
},
});

1519
bun.lock

File diff suppressed because it is too large Load diff

View file

@ -1,592 +0,0 @@
# React Runtime Migration Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** Replace the SvelteKit dashboard app with a React-based Vite app and Bun production server while preserving model-driven dashboard behavior and reusable presentation boundaries.
**Architecture:** The browser runtime becomes React mounted from `src/main.tsx`. A Bun server at `src/server/index.ts` serves the static React build and exposes JSON API routes for dashboard reads and agent dashboard mutations. Existing model, persistence, datasource, and agent-config modules remain framework-agnostic TypeScript with import path updates as needed.
**Tech Stack:** Bun, Vite, React, React DOM, TypeScript, Tailwind CSS v4, shadcn/ui, Vitest, Storybook React Vite, Playwright, Drizzle ORM, Bun SQLite, MSW, uPlot, Iconify React.
---
## File Structure
- Create `index.html`: Vite app shell with `<div id="root"></div>` and `/src/main.tsx`.
- Create `src/main.tsx`: React DOM bootstrap and global CSS import.
- Create `src/App.tsx`: Dashboard fetch, refresh interval, ready/empty/loading/invalid rendering.
- Create `src/App.test.tsx`: React server-rendering and hook behavior tests for dashboard states.
- Create `src/server/index.ts`: Bun HTTP server, static asset serving, API router, production entry.
- Create `src/server/routes/dashboard.ts`: `GET /api/dashboard` runtime loader and datasource resolver.
- Create `src/server/routes/agent-dashboard.ts`: `POST /api/agent/dashboard` adapter for `handleAgentDashboardRequest`.
- Create `src/server/routes/dashboard.test.ts`: dashboard API state and datasource-disable tests.
- Create `src/server/routes/agent-dashboard.test.ts`: agent API delegation/auth tests.
- Create `src/lib/ui/components/*.tsx`: React ports of current reusable Svelte components.
- Create `src/lib/ui/components/styles.css`: component CSS migrated from Svelte style blocks.
- Create `components.json`: shadcn/ui configuration for Vite, Radix, Nova, Tailwind v4, and `$lib` aliases.
- Create `src/lib/components/ui/*.tsx`: selected shadcn primitives, not the full registry.
- Create `src/lib/utils.ts`: `cn()` helper for shadcn and dashboard components.
- Replace `src/lib/ui/components/render.test.ts`: React `renderToString` component tests.
- Replace `src/lib/ui/stories/*.stories.svelte`: React `.stories.tsx` stories.
- Modify `.storybook/main.ts`: use `@storybook/react-vite` and React story globs.
- Modify `.storybook/preview.ts`: use React Storybook types and keep MSW setup/global CSS.
- Modify `vite.config.ts`: use React and Tailwind plugins, alias `$lib` to `src/lib`, build client, and keep Vitest config.
- Modify `tsconfig.json`: remove `.svelte-kit` inheritance, enable JSX, and define path aliases.
- Modify `package.json`: swap Svelte/SvelteKit dependencies for React/Tailwind/shadcn tooling and update scripts.
- Modify `playwright.config.ts`: build React client and Bun server before e2e.
- Modify `Containerfile`: copy React/Bun build artifacts and keep `bun build/index.js` command.
- Modify `README.md`: document React runtime, Bun server, scripts, QA gate, deployment.
- Remove `svelte.config.js`, `src/app.html`, `src/routes/**`, and all `.svelte` files after replacements pass.
## Task 1: React Toolchain And Typecheck Scaffold
**Files:**
- Modify: `package.json`
- Modify: `bun.lock`
- Modify: `tsconfig.json`
- Modify: `vite.config.ts`
- Create: `index.html`
- Create: `src/main.tsx`
- Create: `src/App.tsx`
- Create: `src/App.test.tsx`
- [ ] **Step 1: Write the failing React scaffold test**
Create `src/App.test.tsx`:
```tsx
import { renderToString } from "react-dom/server";
import { describe, expect, test } from "vitest";
import { AppStateView } from "./App";
describe("React app dashboard state view", () => {
test("renders loading dashboard state", () => {
const html = renderToString(
<AppStateView
dashboard={{
state: "loading",
title: "Loading Dashboard",
subtitle: "Fetching active model",
message: "Waiting for the active dashboard document.",
}}
/>,
);
expect(html).toContain("Loading Dashboard");
expect(html).toContain("Fetching active model");
});
});
```
- [ ] **Step 2: Run the test to verify it fails**
Run: `bun run test:unit src/App.test.tsx`
Expected: FAIL because React dependencies and `src/App.tsx` do not exist.
- [ ] **Step 3: Add React dependencies and scaffold files**
Update `package.json` scripts and dependencies:
```json
{
"scripts": {
"dev": "vite --host 0.0.0.0",
"build": "vite build && bun build src/server/index.ts --target bun --outdir build",
"preview": "HOST=0.0.0.0 PORT=4173 bun build/index.js",
"storybook": "storybook dev -p 6006 --host 0.0.0.0",
"build-storybook": "storybook build",
"check": "tsc --noEmit",
"test": "vitest run",
"test:unit": "vitest run",
"test:e2e": "env -u NO_COLOR playwright test",
"test:qa": "bun run check && bun run test:unit && bun run build && bun run build-storybook && bun run test:e2e",
"db:generate": "drizzle-kit generate",
"db:check": "drizzle-kit check"
}
}
```
Install React, Tailwind, and shadcn packages with Bun so `bun.lock` updates:
```sh
bun add @iconify/react @vitejs/plugin-react react react-dom
bun add class-variance-authority clsx lucide-react radix-ui tailwind-merge tw-animate-css @fontsource-variable/geist
bun add -d @storybook/react-vite @tailwindcss/vite @types/react @types/react-dom shadcn tailwindcss
```
Create `index.html`:
```html
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Dimension Lab</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>
```
Create minimal `src/App.tsx`:
```tsx
import type { DashboardRuntimeState } from "$lib/server/dashboard";
export function AppStateView({ dashboard }: { dashboard: DashboardRuntimeState }) {
if (dashboard.state === "ready") {
return <main>{dashboard.document.metadata.title}</main>;
}
return (
<main className="state-shell" data-dashboard-state={dashboard.state}>
<h1>{dashboard.title}</h1>
<p>{dashboard.subtitle}</p>
<p>{dashboard.message}</p>
</main>
);
}
export default function App() {
return (
<AppStateView
dashboard={{
state: "loading",
title: "Loading Dashboard",
subtitle: "Fetching active model",
message: "Waiting for the active dashboard document.",
}}
/>
);
}
```
Create `src/main.tsx`:
```tsx
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import App from "./App";
import "./app.css";
const root = document.getElementById("root");
if (!root) throw new Error("Missing React root element");
createRoot(root).render(
<StrictMode>
<App />
</StrictMode>,
);
```
Update `tsconfig.json` with React JSX and path aliases.
Update `vite.config.ts` to use `@vitejs/plugin-react`, `@tailwindcss/vite`,
and the `$lib` alias. Initialize shadcn with:
```sh
bunx --bun shadcn@latest init --template vite --base radix --preset nova --yes --css-variables
bunx --bun shadcn@latest add badge card progress separator skeleton alert
```
Do not run `bunx --bun shadcn@latest add --all`; the dashboard should only
vendor primitives it actually uses.
- [ ] **Step 4: Run checks for the scaffold**
Run: `bun run test:unit src/App.test.tsx && bun run check`
Expected: PASS.
- [ ] **Step 5: Commit**
```sh
git add package.json bun.lock tsconfig.json vite.config.ts index.html src/main.tsx src/App.tsx src/App.test.tsx
git commit -m "build: add react vite scaffold"
```
## Task 2: Bun Server And Dashboard API
**Files:**
- Create: `src/server/index.ts`
- Create: `src/server/routes/dashboard.ts`
- Create: `src/server/routes/dashboard.test.ts`
- Create: `src/server/routes/agent-dashboard.ts`
- Create: `src/server/routes/agent-dashboard.test.ts`
- Modify: `src/App.tsx`
- Modify: `playwright.config.ts`
- [ ] **Step 1: Write failing API route tests**
Create `src/server/routes/dashboard.test.ts`:
```ts
import { describe, expect, test } from "vitest";
import { loadDashboardResponse } from "./dashboard";
describe("dashboard API route", () => {
test("returns ready dashboard runtime state from the existing model loader", async () => {
const response = await loadDashboardResponse({
disableLiveDatasources: true,
refreshSeedDocument: true,
seedIfEmpty: true,
});
expect(response.state).toBe("ready");
if (response.state !== "ready") throw new Error("expected ready dashboard");
expect(response.document.metadata.title).toContain("Dimension Lab");
});
});
```
Create `src/server/routes/agent-dashboard.test.ts`:
```ts
import { describe, expect, test } from "vitest";
import { handleAgentDashboardRoute } from "./agent-dashboard";
describe("agent dashboard API route", () => {
test("delegates unauthorized requests to the existing agent handler", async () => {
const response = await handleAgentDashboardRoute(
new Request("http://localhost/api/agent/dashboard", { method: "POST" }),
);
expect(response.status).toBe(401);
});
});
```
- [ ] **Step 2: Run tests to verify they fail**
Run: `bun run test:unit src/server/routes/dashboard.test.ts src/server/routes/agent-dashboard.test.ts`
Expected: FAIL because the route modules do not exist.
- [ ] **Step 3: Implement server route modules and server entry**
Implement `loadDashboardResponse()` by calling `loadDashboardRuntime()` and
`resolveDashboardDatasources()` exactly like the current SvelteKit load
function. Implement `handleAgentDashboardRoute()` by returning
`handleAgentDashboardRequest(request)`. Implement `src/server/index.ts` with
Bun.serve routes for `/api/dashboard`, `/api/agent/dashboard`, static Vite
assets, and SPA fallback to `index.html`.
- [ ] **Step 4: Update React app to fetch `/api/dashboard`**
`src/App.tsx` should export `AppStateView` for tests and make the default
`App` fetch dashboard state with `useEffect`. It should clear refresh timers
when state changes and on unmount.
- [ ] **Step 5: Run route and app tests**
Run:
```sh
bun run test:unit src/server/routes/dashboard.test.ts src/server/routes/agent-dashboard.test.ts src/App.test.tsx
```
Expected: PASS.
- [ ] **Step 6: Commit**
```sh
git add src/server src/App.tsx src/App.test.tsx playwright.config.ts
git commit -m "feat(server): add bun dashboard api"
```
## Task 3: React UI Component Library
**Files:**
- Create: `src/lib/ui/components/*.tsx`
- Create: `src/lib/ui/components/styles.css`
- Modify: `src/lib/ui/components/render.test.ts`
- Modify: `src/lib/ui/index.ts`
- Keep: `src/lib/ui/types.ts`
- Keep: `src/lib/ui/model-renderer.ts`
- Keep: `src/lib/ui/fixtures.ts`
- [ ] **Step 1: Replace Svelte SSR tests with failing React render tests**
Rewrite `src/lib/ui/components/render.test.ts` to import React components and
`renderToString` from `react-dom/server`. Keep the current assertions for:
- dashboard fixture content
- generic secondary fixture content
- optional service/status links
- stable model IDs
- progress bar behavior
- uPlot chart surface marker
- native attributes on Button and IconButton
Run: `bun run test:unit src/lib/ui/components/render.test.ts`
Expected: FAIL because React component files do not exist yet.
- [ ] **Step 2: Port atomic components**
Create React equivalents for `Badge`, `Button`, `IconGlyph`, `IconButton`,
`ProgressMeter`, `Separator`, `Sparkline`, `SignalTrace`, `LineChart`, and
`StatusBadge`. Preserve class names and `data-*` attributes from Svelte.
Run: `bun run test:unit src/lib/ui/components/render.test.ts`
Expected: remaining FAILs only for dashboard composite components.
- [ ] **Step 3: Port layout and card components**
Create React equivalents for `Panel`, `CornerBracketFrame`, `GridFrame`,
`DiagonalStripeField`, `ModuleCard`, `TelemetryCard`, `TelemetryGrid`,
`FooterCell`, `FooterStatusCell`, and `StatusStrip`.
Run: `bun run test:unit src/lib/ui/components/render.test.ts`
Expected: remaining FAILs only for service/dashboard shell components.
- [ ] **Step 4: Port service and dashboard shell components**
Create React equivalents for `ServiceRow`, `ServicePanel`,
`ServiceGroupPanel`, `SystemState`, `DashboardHeader`, `DashboardFrame`,
`TelemetryStrip`, and `WeatherModule`.
Run: `bun run test:unit src/lib/ui/components/render.test.ts`
Expected: PASS.
- [ ] **Step 5: Export React components**
Update `src/lib/ui/index.ts` to export `.tsx` React components and continue
exporting fixtures, renderer, and UI types.
Run:
```sh
bun run test:unit src/lib/ui/components/render.test.ts src/lib/ui/model-renderer.test.ts src/lib/ui/content-boundary.test.ts
```
Expected: PASS.
- [ ] **Step 6: Commit**
```sh
git add src/lib/ui/components src/lib/ui/index.ts
git commit -m "feat(ui): port dashboard components to react"
```
## Task 4: React Dashboard App Rendering
**Files:**
- Modify: `src/App.tsx`
- Modify: `src/App.test.tsx`
- Modify: `src/app.css`
- Delete after `src/page.test.tsx` passes: `src/routes/page.test.ts`
- Create: `src/page.test.tsx`
- [ ] **Step 1: Write failing React page tests**
Create `src/page.test.tsx` with React `renderToString` assertions equivalent to
the current Svelte `src/routes/page.test.ts`:
- ready dashboard renders model content
- invalid model state renders validation errors
- empty and loading states render without crashing
Run: `bun run test:unit src/page.test.tsx`
Expected: FAIL until `AppStateView` uses the React `DashboardFrame` and
`SystemState` components.
- [ ] **Step 2: Implement app state rendering**
Use `dashboardDocumentToUiDashboard()` and `DashboardFrame` for ready state.
Use `SystemState` for empty/loading/invalid states. Preserve state shell CSS
and validation error list markup.
- [ ] **Step 3: Run page tests**
Run: `bun run test:unit src/page.test.tsx src/App.test.tsx`
Expected: PASS.
- [ ] **Step 4: Commit**
```sh
git add src/App.tsx src/App.test.tsx src/page.test.tsx src/app.css
git commit -m "feat(app): render dashboard with react"
```
## Task 5: React Storybook
**Files:**
- Modify: `.storybook/main.ts`
- Modify: `.storybook/preview.ts`
- Create: `src/lib/ui/stories/*.stories.tsx`
- Delete after replacement: `src/lib/ui/stories/*.stories.svelte`
- Delete after replacement: `src/lib/ui/stories/FocusPreview.svelte`
- Modify: `src/lib/ui/storybook.test.ts`
- [ ] **Step 1: Update storybook boundary test first**
Change `src/lib/ui/storybook.test.ts` so it requires React `.stories.tsx`
files and rejects `.stories.svelte` files.
Run: `bun run test:unit src/lib/ui/storybook.test.ts`
Expected: FAIL while Svelte stories still exist.
- [ ] **Step 2: Configure React Storybook**
Update `.storybook/main.ts` to use `@storybook/react-vite`, React story globs,
and the existing addons. Update `.storybook/preview.ts` type imports to React
Storybook while preserving global CSS, MSW setup, backgrounds, controls, and
fullscreen layout.
- [ ] **Step 3: Port stories to React**
Create `.stories.tsx` files for each existing Svelte story. Import React
components from `src/lib/ui` and generic story data from
`src/lib/ui/stories/story-data.ts`.
- [ ] **Step 4: Remove Svelte stories and run Storybook checks**
Run:
```sh
bun run test:unit src/lib/ui/storybook.test.ts
bun run build-storybook
```
Expected: PASS.
- [ ] **Step 5: Commit**
```sh
git add .storybook src/lib/ui/stories src/lib/ui/storybook.test.ts
git commit -m "feat(storybook): migrate stories to react"
```
## Task 6: Remove SvelteKit Runtime
**Files:**
- Delete: `svelte.config.js`
- Delete: `src/app.html`
- Delete: `src/routes/**`
- Delete: all remaining `*.svelte`
- Modify: `package.json`
- Modify: `bun.lock`
- Modify: `README.md`
- Modify: `Containerfile`
- Modify: `playwright.config.ts`
- Modify: `src/lib/presentation-boundary.test.ts`
- [ ] **Step 1: Write/adjust cleanup tests**
Add assertions to presentation or storybook boundary tests that no `.svelte`
files remain under `src/`.
Run: `bun run test:unit src/lib/presentation-boundary.test.ts src/lib/ui/storybook.test.ts`
Expected: FAIL while Svelte files remain.
- [ ] **Step 2: Delete Svelte runtime and dependencies**
Remove all Svelte files and Svelte dependencies. Run `bun install
--frozen-lockfile` only after `package.json` and `bun.lock` are consistent, or
run `bun remove` commands to update both together:
```sh
bun remove @iconify/svelte @storybook/addon-svelte-csf @storybook/sveltekit @sveltejs/adapter-node @sveltejs/kit @sveltejs/vite-plugin-svelte svelte svelte-check
```
- [ ] **Step 3: Update docs, container, and e2e build command**
README should describe React, Vite, Bun server, and unchanged persistence.
`Containerfile` should copy the Vite client output and Bun server output.
`playwright.config.ts` should build and start the Bun server.
- [ ] **Step 4: Run cleanup checks**
Run:
```sh
rg -n "\\.svelte|svelte" package.json src .storybook vite.config.ts tsconfig.json README.md Containerfile
bun run check
bun run test:unit
```
Expected: `rg` finds no Svelte app/runtime references except historical docs in
the committed design/plan, and checks pass.
- [ ] **Step 5: Commit**
```sh
git add -A
git commit -m "refactor: remove svelte runtime"
```
## Task 7: QA Gate, PR, Review, And Merge
**Files:**
- Modify only files needed to fix failures found by this task.
- [ ] **Step 1: Run full QA gate**
Run: `bun run test:qa`
Expected: PASS for check, unit tests, production build, Storybook build, and
Playwright desktop/mobile tests.
- [ ] **Step 2: Inspect current diff**
Run:
```sh
git status --short
git diff --stat main...HEAD
git diff --name-only main...HEAD
```
Expected: only React migration files and docs changed.
- [ ] **Step 3: Push and open ready PR**
Run:
```sh
git push -u origin codex/react-migration
```
Open a ready PR against `main` with title:
```text
refactor: migrate dashboard runtime to react
```
- [ ] **Step 4: Independent review**
Dispatch an independent reviewer to inspect the issue goal, spec, plan, and PR
diff in code-review mode. Blocking findings must be fixed on the same branch.
- [ ] **Step 5: Fix review findings and re-run QA**
For each blocking finding, write or update the relevant failing test first,
make the minimal fix, and run the focused test plus `bun run test:qa`.
- [ ] **Step 6: Merge only after green checks and no blocking review findings**
Merge the PR into `main`, sync the worktree back to `main`, and mark the goal
complete only after the completion criteria in the spec are proven by current
state.
## Plan Self-Review
- Spec coverage: Tasks cover React scaffold, Bun API server, UI component
migration, app rendering, Storybook migration, Svelte removal, QA, PR,
independent review, and merge.
- Red-flag scan: The plan has no incomplete-work markers and no unspecified
acceptance gates.
- Type consistency: Public names used across tasks are `AppStateView`,
`loadDashboardResponse`, and `handleAgentDashboardRoute`.

View file

@ -1,436 +0,0 @@
# Turbo Component Library Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** Convert the single-package Dimension Lab website into a Turborepo workspace where `apps/web` consumes a compiled reusable React component library from `packages/ui`.
**Architecture:** The root becomes a private Bun workspace with Turbo orchestration. `packages/ui` owns generic reusable UI components, CSS, theme helpers, Storybook, and package-level tests. `apps/web` owns the website runtime, model/server/database logic, the model-to-UI adapter, e2e tests, and deployment container.
**Tech Stack:** Bun workspaces, Turborepo, Vite, React 19, TypeScript, Storybook React Vite, Tailwind CSS v4, shadcn CSS, Vitest, Playwright, Drizzle ORM, Bun SQLite.
---
## File Structure
- Create root `turbo.json`: cacheable task graph for `build`, `check`, `test:unit`, `build-storybook`, `test:e2e`, and `test:qa`.
- Create root `tsconfig.base.json`: shared strict TypeScript defaults.
- Modify root `package.json`: private workspace root with `apps/*` and `packages/*`, Turbo scripts, and `turbo` dev dependency only.
- Create `packages/ui/package.json`: compiled `@dimensionlab/ui` package with code and CSS exports.
- Create `packages/ui/tsconfig.json` and `packages/ui/tsconfig.build.json`: package typecheck and declaration/JS build config.
- Create `packages/ui/src/styles.css`: library style entry importing font, uPlot CSS, tokens, and component CSS.
- Move `src/lib/ui/components/**` to `packages/ui/src/components/**`.
- Move `src/lib/ui/stories/**` to `packages/ui/src/stories/**`.
- Move `src/lib/ui/tokens.css` to `packages/ui/src/tokens.css`.
- Move `src/lib/ui/theme.ts`, `types.ts`, `format.ts`, `fixtures.ts`, and `index.ts` to `packages/ui/src/**`.
- Move `src/lib/ui/components/styles.css` to `packages/ui/src/components/styles.css`.
- Move `.storybook/**` to `packages/ui/.storybook/**`.
- Move unused shadcn primitives from `src/lib/components/ui/**` to `packages/ui/src/primitives/**` and update their `cn` import to package-local `src/utils.ts`.
- Move `src/lib/utils.ts` to `packages/ui/src/utils.ts`.
- Move app runtime files into `apps/web`: `src/App.tsx`, `src/main.tsx`, `src/app.css`, `src/server/**`, `src/lib/model/**`, `src/lib/server/**`, `src/lib/testing/**`, `src/vite-env.d.ts`, `src/page.test.tsx`, `src/server/dev.ts`, `tests/**`, `drizzle/**`, `Containerfile`, `index.html`, `playwright.config.ts`, `vite.config.ts`, `drizzle.config.ts`, and app-specific README/deployment files.
- Move `src/lib/ui/model-renderer.ts` and `model-renderer.test.ts` into `apps/web/src/lib/ui-adapter/**`.
- Create `apps/web/package.json`, `apps/web/tsconfig.json`, `apps/web/vite.config.ts`, and `apps/web/playwright.config.ts`.
- Update `apps/web/src/App.tsx` to import components/types from `@dimensionlab/ui` and import the adapter from `$lib/ui-adapter/model-renderer`.
- Update `apps/web/src/app.css` to import `@dimensionlab/ui/styles.css` instead of local UI CSS files.
- Update tests that read paths so package boundary tests inspect `packages/ui` and app tests inspect `apps/web`.
- Update `README.md` to describe the workspace commands, package boundaries, Storybook location, and deployment path.
## Task 1: Workspace And Boundary Tests
**Files:**
- Modify: `package.json`
- Create: `turbo.json`
- Create: `tsconfig.base.json`
- Create: `packages/ui/package.json`
- Create: `packages/ui/tsconfig.json`
- Create: `packages/ui/tsconfig.build.json`
- Create: `apps/web/package.json`
- Create: `apps/web/tsconfig.json`
- Create: `apps/web/src/lib/workspace-boundary.test.ts`
- [ ] **Step 1: Write the failing workspace boundary test**
Create `apps/web/src/lib/workspace-boundary.test.ts`:
```ts
import { existsSync, readFileSync } from "node:fs";
import { join } from "node:path";
import { describe, expect, test } from "vitest";
const root = join(import.meta.dir, "../../../..");
describe("workspace boundaries", () => {
test("declares the root as a turbo-managed bun workspace", () => {
const packageJson = JSON.parse(readFileSync(join(root, "package.json"), "utf8")) as {
private?: boolean;
scripts?: Record<string, string>;
workspaces?: string[];
};
expect(packageJson.private).toBe(true);
expect(packageJson.workspaces).toEqual(["apps/*", "packages/*"]);
expect(packageJson.scripts?.build).toBe("turbo build");
expect(existsSync(join(root, "turbo.json"))).toBe(true);
});
test("keeps the website app and reusable UI library as separate packages", () => {
const webPackage = JSON.parse(
readFileSync(join(root, "apps/web/package.json"), "utf8"),
) as { dependencies?: Record<string, string>; name?: string };
const uiPackage = JSON.parse(
readFileSync(join(root, "packages/ui/package.json"), "utf8"),
) as { exports?: Record<string, unknown>; name?: string };
expect(webPackage.name).toBe("@dimensionlab/web");
expect(webPackage.dependencies?.["@dimensionlab/ui"]).toBe("workspace:*");
expect(uiPackage.name).toBe("@dimensionlab/ui");
expect(uiPackage.exports).toHaveProperty(".");
expect(uiPackage.exports).toHaveProperty("./styles.css");
});
});
```
- [ ] **Step 2: Run the test to verify it fails**
Run:
```sh
bunx vitest run apps/web/src/lib/workspace-boundary.test.ts
```
Expected: FAIL because `apps/web`, `packages/ui`, and `turbo.json` do not exist.
- [ ] **Step 3: Add minimal workspace manifests**
Create root `package.json` as the workspace orchestrator:
```json
{
"name": "dimensionlab",
"version": "0.0.1",
"private": true,
"type": "module",
"packageManager": "bun@1.3.14",
"workspaces": ["apps/*", "packages/*"],
"scripts": {
"dev": "turbo dev --filter=@dimensionlab/web",
"build": "turbo build",
"preview": "bun run --cwd apps/web preview",
"storybook": "turbo storybook --filter=@dimensionlab/ui",
"build-storybook": "turbo build-storybook --filter=@dimensionlab/ui",
"check": "turbo check",
"test": "turbo test:unit",
"test:unit": "turbo test:unit",
"test:e2e": "turbo test:e2e --filter=@dimensionlab/web",
"test:qa": "turbo test:qa",
"db:generate": "bun run --cwd apps/web db:generate",
"db:check": "bun run --cwd apps/web db:check"
},
"devDependencies": {
"turbo": "^2.5.0"
}
}
```
Create `turbo.json`:
```json
{
"$schema": "https://turbo.build/schema.json",
"tasks": {
"build": {
"dependsOn": ["^build"],
"outputs": ["dist/**", "build/**"]
},
"check": {
"dependsOn": ["^build"],
"outputs": []
},
"test:unit": {
"dependsOn": ["^build"],
"outputs": []
},
"build-storybook": {
"dependsOn": ["^build"],
"outputs": ["storybook-static/**"]
},
"test:e2e": {
"dependsOn": ["build", "^build"],
"outputs": ["test-results/**", "playwright-report/**"]
},
"test:qa": {
"dependsOn": ["check", "test:unit", "build", "build-storybook", "test:e2e"],
"outputs": []
},
"dev": {
"cache": false,
"persistent": true
},
"storybook": {
"cache": false,
"persistent": true
}
}
}
```
Create minimal `packages/ui/package.json` with `@dimensionlab/ui` exports and
minimal `apps/web/package.json` with `@dimensionlab/ui` as a workspace
dependency. Move the full dependency lists in Task 2 and Task 3.
- [ ] **Step 4: Run the boundary test**
Run:
```sh
bunx vitest run apps/web/src/lib/workspace-boundary.test.ts
```
Expected: PASS.
- [ ] **Step 5: Commit**
```sh
git add package.json turbo.json tsconfig.base.json apps/web/package.json apps/web/tsconfig.json apps/web/src/lib/workspace-boundary.test.ts packages/ui/package.json packages/ui/tsconfig.json packages/ui/tsconfig.build.json
git commit -m "build: add turbo workspace manifests"
```
## Task 2: Extract The UI Package
**Files:**
- Move: `src/lib/ui/components/**` to `packages/ui/src/components/**`
- Move: `src/lib/ui/stories/**` to `packages/ui/src/stories/**`
- Move: `src/lib/ui/{index.ts,types.ts,theme.ts,format.ts,fixtures.ts,tokens.css}` to `packages/ui/src/**`
- Move: `.storybook/**` to `packages/ui/.storybook/**`
- Move: `src/lib/components/ui/**` to `packages/ui/src/primitives/**`
- Move: `src/lib/utils.ts` to `packages/ui/src/utils.ts`
- Create: `packages/ui/src/styles.css`
- Modify: `packages/ui/src/index.ts`
- Modify: `packages/ui/src/storybook.test.ts`
- Modify: `packages/ui/src/content-boundary.test.ts`
- [ ] **Step 1: Write the failing UI isolation assertion**
Update the package boundary tests to read from `packages/ui/src` and assert no
imports from `apps/web`, `$lib/server`, or `$lib/model` exist:
```ts
expect(source).not.toMatch(/from ["'](?:apps\/web|\$lib\/server|\$lib\/model)/);
```
Run:
```sh
bunx vitest run packages/ui/src/content-boundary.test.ts
```
Expected: FAIL until the files move and the app-specific adapter is removed.
- [ ] **Step 2: Move generic UI files**
Run mechanical moves with `git mv`. Move `model-renderer.ts` out of the UI
package in Task 3 rather than into `packages/ui`.
- [ ] **Step 3: Add the UI style entry**
Create `packages/ui/src/styles.css`:
```css
@import "@fontsource-variable/geist";
@import "uplot/dist/uPlot.min.css";
@import "./tokens.css";
@import "./components/styles.css";
```
- [ ] **Step 4: Make package imports relative or package-local**
Update moved primitive files to import `cn` from `../utils`.
Remove `dashboardDocumentToUiDashboard` from `packages/ui/src/index.ts`.
- [ ] **Step 5: Build the package**
Run:
```sh
bun run --cwd packages/ui build
bun run --cwd packages/ui check
bun run --cwd packages/ui test:unit
```
Expected: PASS and `packages/ui/dist` contains `index.js`, `index.d.ts`, and
CSS files.
- [ ] **Step 6: Commit**
```sh
git add packages/ui src/lib/ui src/lib/components src/lib/utils.ts
git commit -m "refactor(ui): extract reusable component package"
```
## Task 3: Move The Web App Workspace
**Files:**
- Move: `src/**` app files that are not reusable UI to `apps/web/src/**`
- Move: `tests/**` to `apps/web/tests/**`
- Move: `drizzle/**` to `apps/web/drizzle/**`
- Move: `Containerfile` to `apps/web/Containerfile`
- Move: `index.html`, `vite.config.ts`, `playwright.config.ts`, `drizzle.config.ts` to `apps/web/**`
- Create: `apps/web/src/lib/ui-adapter/model-renderer.ts`
- Modify: `apps/web/src/App.tsx`
- Modify: `apps/web/src/app.css`
- [ ] **Step 1: Move the model adapter out of UI**
Move `src/lib/ui/model-renderer.ts` to
`apps/web/src/lib/ui-adapter/model-renderer.ts` and update imports from
`$lib/model` plus UI types from `@dimensionlab/ui`.
- [ ] **Step 2: Move app runtime and tests**
Use `git mv` for app/server/model/test/deployment files into `apps/web`.
- [ ] **Step 3: Update app imports**
In `apps/web/src/App.tsx`, import UI components and types from
`@dimensionlab/ui`, and import `dashboardDocumentToUiDashboard` from
`$lib/ui-adapter/model-renderer`.
In `apps/web/src/app.css`, replace local UI imports with:
```css
@import "tailwindcss";
@import "tw-animate-css";
@import "shadcn/tailwind.css";
@import "@dimensionlab/ui/styles.css";
```
- [ ] **Step 4: Run app typecheck and unit tests**
Run:
```sh
bun run --cwd apps/web check
bun run --cwd apps/web test:unit
```
Expected: PASS.
- [ ] **Step 5: Commit**
```sh
git add apps/web src tests drizzle Containerfile index.html vite.config.ts playwright.config.ts drizzle.config.ts
git commit -m "refactor(web): move website into app workspace"
```
## Task 4: Wire Turbo, Storybook, Playwright, And Container
**Files:**
- Modify: `apps/web/playwright.config.ts`
- Modify: `apps/web/Containerfile`
- Modify: `apps/web/vite.config.ts`
- Modify: `packages/ui/.storybook/main.ts`
- Modify: `packages/ui/.storybook/preview.ts`
- Modify: `tests/e2e/storybook-server.ts` after move to `apps/web/tests/e2e/storybook-server.ts`
- Modify: `README.md`
- [ ] **Step 1: Update Playwright commands for workspaces**
In `apps/web/playwright.config.ts`, make the web server command build from the
workspace root or app directory consistently:
```ts
command: `DISABLE_LIVE_DATASOURCES=1 bun run build && DISABLE_LIVE_DATASOURCES=1 DATABASE_URL=${databaseUrl} HOST=127.0.0.1 PORT=${port} bun build/index.js`
```
This command runs inside `apps/web` when invoked through the app package script.
- [ ] **Step 2: Update Storybook package paths**
`packages/ui/.storybook/main.ts` should use `../src/**/*.stories.@(js|ts|tsx)`.
`packages/ui/.storybook/preview.ts` should import `../src/styles.css` and use
generic MSW handlers only if they are moved into the UI package.
- [ ] **Step 3: Update the container build**
`apps/web/Containerfile` should build from the repository root context or copy
only the workspace files it needs. Preserve the runtime command:
```dockerfile
CMD ["bun", "build/index.js"]
```
- [ ] **Step 4: Run full root checks**
Run:
```sh
bun install
bun run check
bun run test:unit
bun run build
bun run build-storybook
bun run test:e2e
```
Expected: PASS.
- [ ] **Step 5: Commit**
```sh
git add README.md apps/web packages/ui package.json turbo.json bun.lock
git commit -m "build: wire turbo workspace qa"
```
## Task 5: Final QA, PR, Review, Merge, Deploy
**Files:**
- No planned source edits unless verification finds blockers.
- [ ] **Step 1: Run release gate**
Run:
```sh
bun run test:qa
```
Expected: PASS.
- [ ] **Step 2: Push and open PR**
Run:
```sh
git push -u origin codex/turbo-component-library
```
Open a ready PR against `main` titled:
```text
refactor: migrate dashboard to turbo component library
```
- [ ] **Step 3: Independent review**
Send an independent reviewer to inspect the PR diff against the objective:
Turbo repo, `apps/web`, compiled `packages/ui`, reusable components removed
from app, Storybook with UI package, root QA passing, no server behavior change.
- [ ] **Step 4: Resolve blockers**
For each blocking review finding, write or update a failing test first, verify
the failure, implement the fix, run targeted checks, commit, push, and re-review.
- [ ] **Step 5: Merge and deploy**
When review and checks are clean, merge the PR into `main`, sync the production
checkout, rebuild the Podman image, restart `dimensionlab-website.service`, and
verify `https://dimensionlab.net/` with a browser smoke check.
## Self-Review
- Spec coverage: every completion criterion in the design maps to Task 1 through
Task 5.
- Placeholder scan: no task says TBD, TODO, or "add tests" without commands.
- Type consistency: package names are consistently `@dimensionlab/ui` and
`@dimensionlab/web`; the app adapter path is consistently
`$lib/ui-adapter/model-renderer`.

View file

@ -1,177 +0,0 @@
# React Runtime Migration Design
## Context
The current Dimension Lab website is a SvelteKit application. It uses Bun,
Vite, Svelte 5, SvelteKit server routes, Svelte Storybook stories, and
Svelte SSR component tests. The app has one dashboard route, one agent
configuration API route, reusable Svelte UI components, typed dashboard model
data, Drizzle-backed SQLite persistence, datasource resolution, Playwright
desktop/mobile checks, and a Bun-based container runtime.
The migration goal is to make the project React-based so dashboard UI
components can be reused outside the current app. A partial React island inside
SvelteKit would leave the app split across two component systems, so the target
state is a React runtime and React component library.
## Target Architecture
Use Vite React for the browser app and a small Bun HTTP server for production
runtime. This keeps the existing Bun SQLite persistence model and avoids adding
a heavier React framework where the current route/API surface is small. Use
Tailwind CSS v4 and shadcn/ui as the reusable primitive layer for React
components, but do not vendor the full shadcn registry. Add only primitives
that map to the dashboard surface.
The migrated app will have these boundaries:
- `src/main.tsx` mounts the React application in the browser.
- `src/App.tsx` owns dashboard loading, refresh timing, document-to-UI mapping,
and non-ready dashboard states.
- `src/server/index.ts` serves the Vite build output in production and exposes
JSON API routes.
- `src/server/routes/dashboard.ts` loads the dashboard runtime and resolves live
datasources unless `DISABLE_LIVE_DATASOURCES=1`.
- `src/server/routes/agent-dashboard.ts` delegates POST requests to the existing
`handleAgentDashboardRequest` function.
- `src/lib/model/**`, `src/lib/server/db/**`, `src/lib/server/dashboard.ts`,
`src/lib/server/datasources/**`, and `src/lib/server/agent-config/**` remain
TypeScript business logic with minimal import-path updates.
- `src/lib/ui/components/*.tsx` contains the reusable React component library.
- `src/lib/ui/stories/*.stories.tsx` contains React Storybook stories.
- `src/lib/components/ui/*.tsx` contains shadcn/ui primitives used by the
dashboard component library.
- `components.json` records the shadcn configuration with Vite, Radix, the Nova
preset, and `$lib` import aliases.
## Component Migration
Every current Svelte UI component will be ported to React with typed props:
- Badge
- Button
- CornerBracketFrame
- DashboardFrame
- DashboardHeader
- DiagonalStripeField
- FooterCell
- FooterStatusCell
- GridFrame
- IconButton
- IconGlyph
- LineChart
- ModuleCard
- Panel
- ProgressMeter
- ScanlineField
- Separator
- ServiceGroupPanel
- ServicePanel
- ServiceRow
- SignalTrace
- Sparkline
- StatusBadge
- StatusStrip
- SystemState
- TelemetryCard
- TelemetryGrid
- TelemetryStrip
- WeatherModule
The visual design, CSS custom property tokens, accessibility attributes,
data-model identifiers, severity attributes, focusable links, reduced-motion
behavior, and screenshot-tested dashboard layout must stay equivalent to the
current Svelte implementation.
CSS will keep the existing design tokens in `src/lib/ui/tokens.css`.
`src/app.css` imports Tailwind, shadcn CSS, font assets, and the existing
Dimension Lab token file. shadcn semantic variables must be mapped to the dark
console palette so generated primitives fit the dashboard instead of resetting
the app to a light generic theme. Shared UI types and
`dashboardDocumentToUiDashboard` remain framework-agnostic TypeScript.
## Runtime And API Behavior
The React app will fetch `GET /api/dashboard` on page load. When the runtime
state is ready, the response includes the dashboard document and metadata. When
the runtime state is empty, loading, or invalid, the React app renders the same
state shell that the Svelte page currently renders.
Ready dashboard documents use `metadata.refreshIntervalSeconds` to schedule a
refresh. The React implementation will clear old timers when the dashboard
state changes and on unmount.
The existing agent configuration endpoint remains available at
`POST /api/agent/dashboard`. The request validation, token authorization,
preview, publish, rollback, JSON patch behavior, and persistence behavior remain
owned by `src/lib/server/agent-config/index.ts`.
## Build, Storybook, And Deployment
`package.json` will move from Svelte/SvelteKit dependencies to React tooling:
- Runtime dependencies include `react`, `react-dom`, `@iconify/react`,
selected shadcn primitive dependencies, Tailwind merge helpers, and existing
non-Svelte libraries that still apply.
- Dev dependencies include `@vitejs/plugin-react`, `@tailwindcss/vite`,
`tailwindcss`, and the shadcn package needed by the generated CSS import.
- Storybook moves from `@storybook/sveltekit` and Svelte CSF to
`@storybook/react-vite`.
- `svelte.config.js`, `src/app.html`, `src/routes/**`, and `.svelte` files are
removed after equivalent React/server files exist.
The production build still creates `build/index.js` as the Bun server entry so
the current container command remains:
```sh
DATABASE_URL=file:/data/dimensionlab.sqlite HOST=0.0.0.0 PORT=3000 bun build/index.js
```
The `Containerfile` continues to install with Bun, build with Bun, copy the
client/server build output plus `drizzle/`, and run the Bun server.
## Testing Strategy
The migration is verified with equivalent or stronger tests:
- TypeScript check covers React TSX, server modules, and shared model code.
- Current model, validation, database, datasource, and agent-config unit tests
remain in place.
- Svelte SSR component tests become React `react-dom/server` tests.
- Page rendering tests become React app/server response tests.
- Storybook boundary tests are updated to require React story files and continue
preventing Dimension Lab-specific content in reusable presentation stories.
- Playwright desktop/mobile tests continue to run against the production Bun
server and the built React app.
- The full QA gate remains `bun run test:qa`.
## Completion Criteria
The migration is complete only when current evidence proves all of these:
- There are no `.svelte` app, component, route, or story files left.
- `package.json` has no Svelte, SvelteKit, or Svelte Storybook dependencies.
- shadcn is configured for Vite/Radix with `$lib` aliases and only selected
primitives, not the full registry.
- `bun run check` passes.
- `bun run test:unit` passes.
- `bun run build` produces the React client build and Bun server entry.
- `bun run build-storybook` passes with React stories.
- `bun run test:e2e` passes on desktop and mobile.
- The dashboard renders from the same validated dashboard model data.
- `POST /api/agent/dashboard` still exercises the existing agent config logic.
- The production container still runs with `bun build/index.js`.
## Migration Approach
The work should be implemented in focused commits on `codex/react-migration`:
1. Establish React/Vite/Bun server scaffolding and tests while keeping the
current Svelte code available for reference.
2. Port reusable UI components to React and update render tests.
3. Port the dashboard app route and refresh behavior to React.
4. Port Storybook stories and presentation boundary tests to React.
5. Remove SvelteKit runtime files and dependencies.
6. Update build, e2e, README, and container behavior.
7. Run the full QA gate, push the branch, open a ready PR, perform independent
review, fix blockers, and merge only after checks and review pass.

View file

@ -1,186 +0,0 @@
# Turbo Component Library Migration Design
## Context
The current Dimension Lab website is a single Bun/Vite React package. It owns
the browser app, Bun production server, dashboard model, persistence, datasource
adapters, reusable UI components, Storybook, Playwright checks, and container
deployment from one `package.json`.
The UI components are already mostly generic and content-free under
`src/lib/ui`, but they are not reusable by another project because they live
inside the app package, depend on app path aliases, and share the app build,
test, and Storybook configuration. One file in that area,
`src/lib/ui/model-renderer.ts`, imports the Dimension Lab dashboard model and
therefore is an app adapter, not reusable component-library code.
The migration goal is to turn the repository into a Turborepo workspace where
the Dimension Lab website consumes a separate reusable React component package.
## Target Repository Shape
Use one application workspace and one component-library workspace:
```text
.
├── apps/
│ └── web/
│ ├── src/
│ ├── tests/
│ ├── drizzle/
│ ├── Containerfile
│ └── package.json
├── packages/
│ └── ui/
│ ├── src/
│ ├── .storybook/
│ ├── package.json
│ └── tsconfig.json
├── package.json
├── turbo.json
├── tsconfig.base.json
└── bun.lock
```
The root package is private and contains only workspace orchestration:
workspaces, Turbo scripts, shared dev dependencies where useful, and the lock
file. Runtime dependencies belong to the workspace that imports them.
## Package Ownership
`packages/ui` is a compiled React library named `@dimensionlab/ui`.
It owns:
- Reusable React components currently under `src/lib/ui/components`.
- UI CSS tokens and component styles.
- Theme helpers currently under `src/lib/ui/theme.ts`.
- Generic UI prop/data types currently under `src/lib/ui/types.ts`.
- Generic formatting helpers currently under `src/lib/ui/format.ts`.
- Generic Storybook stories and story fixtures.
- UI render tests, boundary tests, and Storybook inventory tests.
It must not import from the website app, the dashboard model, server modules,
database modules, datasource modules, or deployment files.
The library publishes explicit package exports:
- `@dimensionlab/ui` for component and type exports.
- `@dimensionlab/ui/styles.css` for the combined token/component CSS entry.
- Optional explicit subpath exports for future direct imports where useful.
The compiled output goes to `packages/ui/dist` and includes JavaScript,
declaration files, and copied CSS. The package stays private for now but is
structured so it can later be published or moved into another Dimension Lab repo
without taking the website runtime with it.
`apps/web` owns:
- The Vite React browser app.
- The Bun production server and API routes.
- Dashboard model, fixtures, schema, validation, and model migrations.
- Drizzle/SQLite persistence and checked-in SQL migrations.
- Datasource adapters and runtime dashboard loading.
- Agent dashboard configuration endpoint.
- The `dashboardDocumentToUiDashboard` adapter that maps the app model to UI
package props.
- Playwright e2e tests and deployment container.
## Why A Compiled Package
The component package should be compiled rather than a just-in-time source
package. This is slightly more setup, but it better fits reuse outside the
current app because consumers can import stable JavaScript and declarations
instead of relying on their bundler to transpile this repo's TypeScript source.
It also gives Turbo a cacheable `@dimensionlab/ui#build` task.
## Build And Task Graph
Root scripts delegate through Turbo:
- `bun run dev` runs the web dev server and any required dependency tasks.
- `bun run build` runs package builds in dependency order.
- `bun run check` runs TypeScript checks for all workspaces.
- `bun run test:unit` runs Vitest unit tests for all workspaces.
- `bun run build-storybook` builds Storybook from `packages/ui`.
- `bun run test:e2e` runs the web app Playwright suite.
- `bun run test:qa` is the full release gate.
`turbo.json` defines `build`, `check`, `test:unit`, `build-storybook`,
`test:e2e`, and `test:qa` tasks. Build outputs include `dist/**`,
`storybook-static/**`, and `build/**` as appropriate.
The web app depends on `@dimensionlab/ui` using Bun workspace syntax. The web
Vite config aliases `$lib` to `apps/web/src/lib`; the UI package should not use
that app alias.
## Storybook
Storybook moves with the component package. It should load
`@dimensionlab/ui/styles.css`, use React Vite Storybook, and keep the existing
generic story inventory. Environment-specific Dimension Lab labels, hostnames,
links, fallback values, and datasource names remain forbidden in package UI
source and stories.
The repository should no longer have a root Storybook tied to the web app.
## Deployment
The deployed website remains the same service from the outside:
- Production command remains `bun build/index.js` inside the runtime image.
- The container still exposes port `3000` and mounts `/data`.
- Runtime environment variables and database behavior remain unchanged.
The `Containerfile` moves to `apps/web/Containerfile` or remains root with
updated workspace-aware copy/build steps. The chosen layout must preserve the
existing Podman service contract used by `dimensionlab-website.service`.
## Testing Strategy
The migration must add or update tests that prove the new boundaries:
- The root package is a Bun workspace with `apps/*` and `packages/*`.
- The web app imports UI from `@dimensionlab/ui`, not from local copied
component files.
- `packages/ui` does not import from `apps/web`, `$lib/server`, `$lib/model`,
or any website runtime module.
- `dashboardDocumentToUiDashboard` lives in `apps/web` and is tested there.
- Storybook inventory is evaluated against `packages/ui`.
- The full QA gate still covers typecheck, unit tests, app build, Storybook
build, and Playwright desktop/mobile checks.
## Completion Criteria
The migration is complete only when current evidence proves all of these:
- Root `package.json` is a private workspace root with Turbo scripts.
- `turbo.json` exists and models the workspace task graph.
- The web application lives under `apps/web`.
- The reusable React component library lives under `packages/ui`.
- `packages/ui/package.json` is named `@dimensionlab/ui` and has compiled
exports for code, types, and CSS.
- The web app depends on `@dimensionlab/ui` through the workspace.
- Reusable components and Storybook have been removed from the web app package.
- App-specific model/server/database/datasource code has not moved into
`packages/ui`.
- `dashboardDocumentToUiDashboard` is outside the UI package.
- `bun run check` passes from the root.
- `bun run test:unit` passes from the root.
- `bun run build` passes from the root.
- `bun run build-storybook` passes from the root.
- `bun run test:e2e` passes from the root.
- The production container can still be built and run with the same service
contract.
## Migration Approach
Implement this on branch `codex/turbo-component-library` in focused commits:
1. Add the workspace/Turbo scaffolding and boundary tests.
2. Move UI code and Storybook to `packages/ui`.
3. Move the app runtime into `apps/web` and wire it to `@dimensionlab/ui`.
4. Move the model-to-UI adapter into the web app.
5. Update build, test, Playwright, Storybook, README, and container paths.
6. Run the full QA gate, push a ready PR, perform independent review, resolve
blockers, merge to `main`, and deploy only when checks and review are clean.

View file

@ -1,28 +1,22 @@
{
"name": "dimensionlab",
"name": "dimensionlab-website",
"version": "0.0.1",
"private": true,
"type": "module",
"packageManager": "bun@1.3.14",
"workspaces": [
"apps/*",
"packages/*"
],
"scripts": {
"dev": "turbo run dev --filter=@dimensionlab/web",
"build": "turbo run build",
"preview": "turbo run preview --filter=@dimensionlab/web",
"storybook": "turbo run storybook --filter=@dimensionlab/ui",
"build-storybook": "turbo run build-storybook --filter=@dimensionlab/ui",
"check": "turbo run check",
"test": "turbo run test:unit",
"test:unit": "turbo run test:unit",
"test:e2e": "turbo run test:e2e --filter=@dimensionlab/web",
"test:qa": "turbo run check test:unit build @dimensionlab/ui#build-storybook @dimensionlab/web#test:e2e",
"db:generate": "turbo run db:generate --filter=@dimensionlab/web",
"db:check": "turbo run db:check --filter=@dimensionlab/web"
"dev": "vite --host 0.0.0.0",
"build": "vite build",
"preview": "vite preview --host 0.0.0.0",
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json"
},
"devDependencies": {
"turbo": "^2.5.0"
"@sveltejs/adapter-node": "^5.5.4",
"@sveltejs/kit": "^2.65.2",
"@sveltejs/vite-plugin-svelte": "^7.1.2",
"@types/node": "^25.9.3",
"svelte": "^5.56.3",
"svelte-check": "^4.6.0",
"typescript": "^6.0.3",
"vite": "^8.0.16"
}
}

View file

@ -1,36 +0,0 @@
{
"name": "@dimensionlab/dashboard-model",
"version": "0.0.1",
"private": true,
"type": "module",
"exports": {
".": {
"types": "./dist/index.d.ts",
"development": "./src/index.ts",
"default": "./dist/index.js"
},
"./fixtures": {
"types": "./dist/fixtures/index.d.ts",
"development": "./src/fixtures/index.ts",
"default": "./dist/fixtures/index.js"
}
},
"scripts": {
"build": "rm -rf dist && tsc -p tsconfig.build.json",
"check": "tsc --noEmit",
"test": "vitest run",
"test:unit": "vitest run"
},
"dependencies": {
"@sinclair/typebox": "^0.34.49",
"ajv": "^8.20.0",
"ajv-formats": "^3.0.1"
},
"devDependencies": {
"@types/bun": "^1.3.14",
"@types/node": "^25.9.3",
"bun-types": "^1.3.14",
"typescript": "^6.0.3",
"vitest": "^4.1.9"
}
}

View file

@ -1,91 +0,0 @@
import {
DASHBOARD_SCHEMA_VERSION,
type DashboardDocument,
} from "../schema";
export const genericDashboardFixture: DashboardDocument = {
schemaVersion: DASHBOARD_SCHEMA_VERSION,
metadata: {
title: "Operations Console",
subtitle: "Generic environment",
description: "Portable fixture for component and renderer tests.",
timezone: "UTC",
refreshIntervalSeconds: 30,
},
layout: {
density: "dense",
telemetry: ["service-uptime", "queue-depth"],
serviceGroups: ["core-services"],
statusStrips: ["runtime"],
modules: ["ambient"],
},
telemetry: [
{
id: "service-uptime",
label: "Service Uptime",
value: { kind: "percent", value: 99.9, precision: 1 },
detail: "last 30 days",
severity: "ok",
datasource: { type: "static", label: "fixture" },
sparkline: [99.7, 99.8, 99.9, 99.9],
},
{
id: "queue-depth",
label: "Queue Depth",
value: { kind: "number", value: 18 },
detail: "pending jobs",
severity: "warning",
thresholds: { warning: 15, danger: 50 },
datasource: { type: "placeholder", reason: "adapter pending" },
sparkline: [6, 11, 13, 18],
},
],
serviceGroups: [
{
id: "core-services",
title: "Core Services",
layout: "list",
services: [
{
id: "identity",
label: "Identity",
description: "Authentication and profile service",
icon: "mdi:account-key-outline",
severity: "ok",
detail: "ready",
datasource: { type: "static", label: "fixture" },
},
{
id: "scheduler",
label: "Scheduler",
description: "Background task coordinator",
icon: "mdi:calendar-clock",
severity: "warning",
detail: "delayed",
datasource: { type: "placeholder", reason: "health adapter pending" },
},
],
},
],
statusStrips: [
{
id: "runtime",
items: [
{ id: "status", label: "System Status", value: "Degraded", severity: "warning" },
{ id: "sync", label: "Last Sync", value: "2 minutes ago", severity: "stale" },
],
},
],
modules: [
{
id: "ambient",
kind: "summary",
title: "Environment",
value: "Nominal",
detail: "static fixture",
icon: "mdi:radar",
severity: "ok",
datasource: { type: "static", label: "fixture" },
},
],
};

View file

@ -1 +0,0 @@
export { genericDashboardFixture } from "./generic";

View file

@ -1,29 +0,0 @@
export {
DASHBOARD_SCHEMA_VERSION,
DashboardDocumentSchema,
DatasourceReferenceSchema,
ServiceEntrySchema,
ServiceGroupSchema,
TelemetryCardSchema,
ThresholdSchema,
dashboardDocumentJsonSchema,
type DashboardDocument,
type DashboardModule,
type DatasourceReference,
type MetricValue,
type ServiceEntry,
type ServiceGroup,
type Severity,
type StatusItem,
type StatusStrip,
type TelemetryCard,
} from "./schema";
export {
assertDashboardDocument,
formatValidationErrors,
isDashboardDocument,
validateDashboardDocument,
type DashboardValidationFailure,
type DashboardValidationResult,
type DashboardValidationSuccess,
} from "./validation";

View file

@ -1,197 +0,0 @@
import { describe, expect, it } from "vitest";
import {
DASHBOARD_SCHEMA_VERSION,
dashboardDocumentJsonSchema,
validateDashboardDocument,
} from ".";
import {
genericDashboardFixture,
} from "./fixtures";
describe("dashboard model validation", () => {
it("accepts the generic dashboard fixture", () => {
const result = validateDashboardDocument(genericDashboardFixture);
expect(result.valid).toBe(true);
if (result.valid) {
expect(result.data.schemaVersion).toBe(DASHBOARD_SCHEMA_VERSION);
}
});
it("rejects documents with an unsupported schema version", () => {
const invalid = {
...genericDashboardFixture,
schemaVersion: "dashboard.v0",
};
const result = validateDashboardDocument(invalid);
expect(result.valid).toBe(false);
if (!result.valid) {
expect(result.errors.join(" ")).toContain("must be equal to constant");
expect(result.details.length).toBeGreaterThan(0);
}
});
it("returns actionable field paths for invalid documents", () => {
const invalid = JSON.parse(JSON.stringify(genericDashboardFixture));
delete invalid.metadata.title;
invalid.telemetry[0].severity = "fine";
const result = validateDashboardDocument(invalid);
expect(result.valid).toBe(false);
if (!result.valid) {
expect(result.errors.some((error) => error.includes("/metadata"))).toBe(true);
expect(result.errors.some((error) => error.includes("/telemetry/0/severity"))).toBe(true);
}
});
it("includes offending additional property names in errors", () => {
const invalid = JSON.parse(JSON.stringify(genericDashboardFixture));
invalid.metadata.unexpected = true;
const result = validateDashboardDocument(invalid);
expect(result.valid).toBe(false);
if (!result.valid) {
expect(result.errors.join(" ")).toContain("unexpected");
}
});
it("rejects non-finite numbers that cannot roundtrip through JSON", () => {
const invalid = JSON.parse(JSON.stringify(genericDashboardFixture));
invalid.telemetry[0].value.value = Number.NaN;
const result = validateDashboardDocument(invalid);
expect(result.valid).toBe(false);
if (!result.valid) {
expect(result.errors.join(" ")).toContain("finite JSON number");
}
});
it("rejects dangling layout references", () => {
const invalid = JSON.parse(JSON.stringify(genericDashboardFixture));
invalid.layout.telemetry.push("missing-card");
const result = validateDashboardDocument(invalid);
expect(result.valid).toBe(false);
if (!result.valid) {
expect(result.errors.join(" ")).toContain("missing-card");
expect(result.errors.join(" ")).toContain("must reference an existing item");
}
});
it("rejects duplicate IDs within collections", () => {
const invalid = JSON.parse(JSON.stringify(genericDashboardFixture));
invalid.telemetry[1].id = invalid.telemetry[0].id;
const result = validateDashboardDocument(invalid);
expect(result.valid).toBe(false);
if (!result.valid) {
expect(result.errors.join(" ")).toContain("must be unique");
expect(result.errors.join(" ")).toContain(invalid.telemetry[0].id);
}
});
it("rejects duplicate status strip item IDs", () => {
const invalid = JSON.parse(JSON.stringify(genericDashboardFixture));
invalid.statusStrips[0].items.push({
...invalid.statusStrips[0].items[0],
});
const result = validateDashboardDocument(invalid);
expect(result.valid).toBe(false);
if (!result.valid) {
expect(result.errors.join(" ")).toContain("must be unique");
expect(result.errors.join(" ")).toContain(invalid.statusStrips[0].items[0].id);
}
});
it("rejects string values for numeric metric kinds", () => {
const invalid = JSON.parse(JSON.stringify(genericDashboardFixture));
invalid.telemetry[0].value.value = "not a percent";
const result = validateDashboardDocument(invalid);
expect(result.valid).toBe(false);
});
it("rejects percent values outside 0 to 100", () => {
const invalid = JSON.parse(JSON.stringify(genericDashboardFixture));
invalid.telemetry[0].value.value = 150;
const result = validateDashboardDocument(invalid);
expect(result.valid).toBe(false);
});
it("rejects negative values for nonnegative metric kinds", () => {
const invalid = JSON.parse(JSON.stringify(genericDashboardFixture));
invalid.telemetry[0].value = { kind: "latency", value: -20 };
const result = validateDashboardDocument(invalid);
expect(result.valid).toBe(false);
});
it("rejects contradictory warning and danger thresholds", () => {
const invalid = JSON.parse(JSON.stringify(genericDashboardFixture));
invalid.telemetry[0].thresholds = { warning: 90, danger: 80 };
const result = validateDashboardDocument(invalid);
expect(result.valid).toBe(false);
if (!result.valid) {
expect(result.errors.join(" ")).toContain("warning threshold");
}
});
it("rejects percent thresholds above 100", () => {
const invalid = JSON.parse(JSON.stringify(genericDashboardFixture));
invalid.telemetry[0].thresholds = { warning: 99, danger: 999 };
const result = validateDashboardDocument(invalid);
expect(result.valid).toBe(false);
if (!result.valid) {
expect(result.errors.join(" ")).toContain("percent thresholds");
}
});
it("rejects thresholds on text metric values", () => {
const invalid = JSON.parse(JSON.stringify(genericDashboardFixture));
invalid.telemetry[0].value = { kind: "text", value: "available" };
invalid.telemetry[0].thresholds = { warning: 10 };
const result = validateDashboardDocument(invalid);
expect(result.valid).toBe(false);
if (!result.valid) {
expect(result.errors.join(" ")).toContain("text metric values");
}
});
it("rejects undefined properties because they are not JSON values", () => {
const invalid = JSON.parse(JSON.stringify(genericDashboardFixture));
invalid.serviceGroups[0].services[0].link = undefined;
const result = validateDashboardDocument(invalid);
expect(result.valid).toBe(false);
if (!result.valid) {
expect(result.errors.join(" ")).toContain("must be omitted instead of undefined");
}
});
it("exports JSON Schema for external tool contracts", () => {
expect(dashboardDocumentJsonSchema.$id).toContain("dashboard-document.v1");
expect(dashboardDocumentJsonSchema.properties).toHaveProperty("schemaVersion");
expect(dashboardDocumentJsonSchema.properties).toHaveProperty("telemetry");
expect(dashboardDocumentJsonSchema.properties).toHaveProperty("serviceGroups");
});
});

View file

@ -1,252 +0,0 @@
import { Type, type Static } from "@sinclair/typebox";
export const DASHBOARD_SCHEMA_VERSION = "dashboard.v1" as const;
const IdentifierSchema = Type.String({
minLength: 1,
pattern: "^[a-z0-9][a-z0-9-_.:]*$",
});
const SeveritySchema = Type.Union([
Type.Literal("neutral"),
Type.Literal("ok"),
Type.Literal("warning"),
Type.Literal("danger"),
Type.Literal("stale"),
Type.Literal("unavailable"),
]);
const IconReferenceSchema = Type.String({ minLength: 1 });
const LinkSchema = Type.Object(
{
href: Type.String({ format: "uri" }),
label: Type.Optional(Type.String({ minLength: 1 })),
external: Type.Optional(Type.Boolean()),
},
{ additionalProperties: false },
);
export const StaticDatasourceSchema = Type.Object(
{
type: Type.Literal("static"),
label: Type.Optional(Type.String({ minLength: 1 })),
updatedAt: Type.Optional(Type.String({ format: "date-time" })),
},
{ additionalProperties: false },
);
export const PlaceholderDatasourceSchema = Type.Object(
{
type: Type.Literal("placeholder"),
reason: Type.String({ minLength: 1 }),
},
{ additionalProperties: false },
);
export const ExternalDatasourceSchema = Type.Object(
{
type: Type.Literal("external"),
adapter: Type.Union([
Type.Literal("prometheus"),
Type.Literal("http-status"),
Type.Literal("weather"),
Type.Literal("custom"),
]),
reference: Type.String({ minLength: 1 }),
},
{ additionalProperties: false },
);
export const DatasourceReferenceSchema = Type.Union([
StaticDatasourceSchema,
PlaceholderDatasourceSchema,
ExternalDatasourceSchema,
]);
const PercentMetricValueSchema = Type.Object(
{
kind: Type.Literal("percent"),
value: Type.Number({ minimum: 0, maximum: 100 }),
unit: Type.Optional(Type.String({ minLength: 1 })),
precision: Type.Optional(Type.Integer({ minimum: 0, maximum: 4 })),
},
{ additionalProperties: false },
);
const NonNegativeMetricValueSchema = Type.Object(
{
kind: Type.Union([
Type.Literal("bytes"),
Type.Literal("temperature"),
Type.Literal("latency"),
Type.Literal("number"),
]),
value: Type.Number({ minimum: 0 }),
unit: Type.Optional(Type.String({ minLength: 1 })),
precision: Type.Optional(Type.Integer({ minimum: 0, maximum: 4 })),
},
{ additionalProperties: false },
);
const TextMetricValueSchema = Type.Object(
{
kind: Type.Literal("text"),
value: Type.String(),
unit: Type.Optional(Type.String({ minLength: 1 })),
},
{ additionalProperties: false },
);
export const MetricValueSchema = Type.Union([
PercentMetricValueSchema,
NonNegativeMetricValueSchema,
TextMetricValueSchema,
]);
export const ThresholdSchema = Type.Object(
{
warning: Type.Optional(Type.Number({ minimum: 0 })),
danger: Type.Optional(Type.Number({ minimum: 0 })),
},
{ additionalProperties: false, minProperties: 1 },
);
export const TelemetryCardSchema = Type.Object(
{
id: IdentifierSchema,
label: Type.String({ minLength: 1 }),
description: Type.Optional(Type.String()),
icon: Type.Optional(IconReferenceSchema),
value: MetricValueSchema,
detail: Type.Optional(Type.String()),
severity: SeveritySchema,
thresholds: Type.Optional(ThresholdSchema),
datasource: Type.Optional(DatasourceReferenceSchema),
sparkline: Type.Optional(Type.Array(Type.Number())),
},
{ additionalProperties: false },
);
export const ServiceEntrySchema = Type.Object(
{
id: IdentifierSchema,
label: Type.String({ minLength: 1 }),
description: Type.String(),
icon: Type.Optional(IconReferenceSchema),
link: Type.Optional(LinkSchema),
severity: SeveritySchema,
detail: Type.Optional(Type.String()),
datasource: Type.Optional(DatasourceReferenceSchema),
},
{ additionalProperties: false },
);
export const ServiceGroupSchema = Type.Object(
{
id: IdentifierSchema,
title: Type.String({ minLength: 1 }),
layout: Type.Optional(Type.Union([Type.Literal("list"), Type.Literal("grid")])),
services: Type.Array(ServiceEntrySchema),
},
{ additionalProperties: false },
);
export const StatusItemSchema = Type.Object(
{
id: IdentifierSchema,
label: Type.String({ minLength: 1 }),
value: Type.String(),
severity: Type.Optional(SeveritySchema),
link: Type.Optional(LinkSchema),
},
{ additionalProperties: false },
);
export const StatusStripSchema = Type.Object(
{
id: IdentifierSchema,
title: Type.Optional(Type.String({ minLength: 1 })),
items: Type.Array(StatusItemSchema),
},
{ additionalProperties: false },
);
export const DashboardModuleSchema = Type.Object(
{
id: IdentifierSchema,
kind: Type.Union([
Type.Literal("summary"),
Type.Literal("weather"),
Type.Literal("custom"),
]),
title: Type.Optional(Type.String({ minLength: 1 })),
label: Type.Optional(Type.String()),
value: Type.Optional(Type.String()),
detail: Type.Optional(Type.String()),
icon: Type.Optional(IconReferenceSchema),
severity: Type.Optional(SeveritySchema),
datasource: Type.Optional(DatasourceReferenceSchema),
},
{ additionalProperties: false },
);
const LayoutSchema = Type.Object(
{
density: Type.Optional(Type.Union([Type.Literal("compact"), Type.Literal("dense")])),
telemetry: Type.Array(IdentifierSchema),
serviceGroups: Type.Array(IdentifierSchema),
statusStrips: Type.Array(IdentifierSchema),
modules: Type.Optional(Type.Array(IdentifierSchema)),
},
{ additionalProperties: false },
);
const MetadataSchema = Type.Object(
{
title: Type.String({ minLength: 1 }),
subtitle: Type.Optional(Type.String()),
description: Type.Optional(Type.String()),
timezone: Type.Optional(Type.String({ minLength: 1 })),
refreshIntervalSeconds: Type.Optional(Type.Integer({ minimum: 5 })),
},
{ additionalProperties: false },
);
export const DashboardDocumentSchema = Type.Object(
{
schemaVersion: Type.Literal(DASHBOARD_SCHEMA_VERSION),
metadata: MetadataSchema,
layout: LayoutSchema,
telemetry: Type.Array(TelemetryCardSchema),
serviceGroups: Type.Array(ServiceGroupSchema),
statusStrips: Type.Array(StatusStripSchema),
modules: Type.Optional(Type.Array(DashboardModuleSchema)),
migration: Type.Optional(
Type.Object(
{
previousVersion: Type.Optional(Type.String({ minLength: 1 })),
notes: Type.Optional(Type.String()),
},
{ additionalProperties: false },
),
),
},
{
$id: "https://dimensionlab.net/schemas/dashboard-document.v1.json",
additionalProperties: false,
},
);
export type Severity = Static<typeof SeveritySchema>;
export type DatasourceReference = Static<typeof DatasourceReferenceSchema>;
export type MetricValue = Static<typeof MetricValueSchema>;
export type TelemetryCard = Static<typeof TelemetryCardSchema>;
export type ServiceEntry = Static<typeof ServiceEntrySchema>;
export type ServiceGroup = Static<typeof ServiceGroupSchema>;
export type StatusItem = Static<typeof StatusItemSchema>;
export type StatusStrip = Static<typeof StatusStripSchema>;
export type DashboardModule = Static<typeof DashboardModuleSchema>;
export type DashboardDocument = Static<typeof DashboardDocumentSchema>;
export const dashboardDocumentJsonSchema = DashboardDocumentSchema;

View file

@ -1,322 +0,0 @@
import Ajv, { type ErrorObject } from "ajv";
import addFormats from "ajv-formats";
import {
DashboardDocumentSchema,
type DashboardDocument,
} from "./schema";
type DashboardValidationIssue = ErrorObject | SemanticValidationIssue;
interface SemanticValidationIssue {
instancePath: string;
schemaPath: string;
keyword: "semantic";
params: Record<string, unknown>;
message: string;
}
export interface DashboardValidationFailure {
valid: false;
errors: string[];
details: DashboardValidationIssue[];
}
export interface DashboardValidationSuccess {
valid: true;
data: DashboardDocument;
}
export type DashboardValidationResult =
| DashboardValidationFailure
| DashboardValidationSuccess;
const ajv = addFormats(
new Ajv({
allErrors: true,
strict: false,
strictNumbers: true,
}),
);
const validateDashboard = ajv.compile<DashboardDocument>(DashboardDocumentSchema);
export function formatValidationErrors(
errors: DashboardValidationIssue[] = [],
): string[] {
return errors.map((error) => {
const path = error.instancePath || "/";
const suffix = formatErrorParams(error);
const message = error.message || "is invalid";
return `${path} ${message}${suffix}`;
});
}
export function validateDashboardDocument(
value: unknown,
): DashboardValidationResult {
const finiteNumberIssues: SemanticValidationIssue[] = [];
const undefinedIssues: SemanticValidationIssue[] = [];
collectFiniteNumberIssues(value, "", finiteNumberIssues);
collectUndefinedIssues(value, "", undefinedIssues);
if (validateDashboard(value)) {
const semanticIssues = [
...finiteNumberIssues,
...undefinedIssues,
...validateSemanticRules(value),
];
if (semanticIssues.length === 0) {
return { valid: true, data: value };
}
return {
valid: false,
errors: formatValidationErrors(semanticIssues),
details: semanticIssues,
};
}
const details = [
...(validateDashboard.errors || []),
...finiteNumberIssues,
...undefinedIssues,
];
return {
valid: false,
errors: formatValidationErrors(details),
details,
};
}
export function assertDashboardDocument(
value: unknown,
): asserts value is DashboardDocument {
const result = validateDashboardDocument(value);
if (!result.valid) {
throw new Error(`Invalid dashboard document: ${result.errors.join("; ")}`);
}
}
export function isDashboardDocument(value: unknown): value is DashboardDocument {
return validateDashboardDocument(value).valid;
}
function formatErrorParams(error: DashboardValidationIssue): string {
if (error.keyword === "additionalProperties") {
const additionalProperty = error.params.additionalProperty;
return typeof additionalProperty === "string"
? `: ${additionalProperty}`
: "";
}
if (error.keyword === "semantic") {
const id = error.params.id;
const ref = error.params.ref;
if (typeof id === "string") return `: ${id}`;
if (typeof ref === "string") return `: ${ref}`;
}
return "";
}
function validateSemanticRules(document: DashboardDocument): SemanticValidationIssue[] {
const issues: SemanticValidationIssue[] = [];
collectDuplicateIdIssues("telemetry", document.telemetry, issues);
collectDuplicateIdIssues("serviceGroups", document.serviceGroups, issues);
collectDuplicateIdIssues("statusStrips", document.statusStrips, issues);
collectDuplicateIdIssues("modules", document.modules || [], issues);
document.serviceGroups.forEach((group, groupIndex) => {
collectDuplicateIdIssues(
`serviceGroups/${groupIndex}/services`,
group.services,
issues,
);
});
document.statusStrips.forEach((strip, stripIndex) => {
collectDuplicateIdIssues(
`statusStrips/${stripIndex}/items`,
strip.items,
issues,
);
});
collectMissingReferenceIssues(
"layout/telemetry",
document.layout.telemetry,
new Set(document.telemetry.map((item) => item.id)),
issues,
);
collectMissingReferenceIssues(
"layout/serviceGroups",
document.layout.serviceGroups,
new Set(document.serviceGroups.map((item) => item.id)),
issues,
);
collectMissingReferenceIssues(
"layout/statusStrips",
document.layout.statusStrips,
new Set(document.statusStrips.map((item) => item.id)),
issues,
);
collectMissingReferenceIssues(
"layout/modules",
document.layout.modules || [],
new Set((document.modules || []).map((item) => item.id)),
issues,
);
document.telemetry.forEach((card, index) => {
if (card.value.kind === "text" && card.thresholds !== undefined) {
issues.push(
semanticIssue(
`/telemetry/${index}/thresholds`,
"must not be set for text metric values",
{ id: card.id },
),
);
}
if (
card.value.kind === "percent" &&
((card.thresholds?.warning !== undefined && card.thresholds.warning > 100) ||
(card.thresholds?.danger !== undefined && card.thresholds.danger > 100))
) {
issues.push(
semanticIssue(
`/telemetry/${index}/thresholds`,
"percent thresholds must be between 0 and 100",
{ id: card.id },
),
);
}
const warning = card.thresholds?.warning;
const danger = card.thresholds?.danger;
if (warning !== undefined && danger !== undefined && warning > danger) {
issues.push(
semanticIssue(
`/telemetry/${index}/thresholds`,
"warning threshold must be less than or equal to danger threshold",
{ id: card.id },
),
);
}
});
return issues;
}
function collectUndefinedIssues(
value: unknown,
path: string,
issues: SemanticValidationIssue[],
) {
if (value === undefined) {
issues.push(
semanticIssue(path || "/", "must be omitted instead of undefined", {}),
);
return;
}
if (Array.isArray(value)) {
value.forEach((item, index) => {
collectUndefinedIssues(item, `${path}/${index}`, issues);
});
return;
}
if (value && typeof value === "object") {
Object.entries(value).forEach(([key, item]) => {
collectUndefinedIssues(item, `${path}/${escapeJsonPointer(key)}`, issues);
});
}
}
function collectFiniteNumberIssues(
value: unknown,
path: string,
issues: SemanticValidationIssue[],
) {
if (typeof value === "number") {
if (!Number.isFinite(value)) {
issues.push(
semanticIssue(path || "/", "must be a finite JSON number", {}),
);
}
return;
}
if (Array.isArray(value)) {
value.forEach((item, index) => {
collectFiniteNumberIssues(item, `${path}/${index}`, issues);
});
return;
}
if (value && typeof value === "object") {
Object.entries(value).forEach(([key, item]) => {
collectFiniteNumberIssues(item, `${path}/${escapeJsonPointer(key)}`, issues);
});
}
}
function collectDuplicateIdIssues(
collectionPath: string,
items: Array<{ id: string }>,
issues: SemanticValidationIssue[],
) {
const seen = new Set<string>();
items.forEach((item, index) => {
if (seen.has(item.id)) {
issues.push(
semanticIssue(
`/${collectionPath}/${index}/id`,
"must be unique within its collection",
{ id: item.id },
),
);
return;
}
seen.add(item.id);
});
}
function collectMissingReferenceIssues(
layoutPath: string,
refs: string[],
validIds: Set<string>,
issues: SemanticValidationIssue[],
) {
refs.forEach((ref, index) => {
if (!validIds.has(ref)) {
issues.push(
semanticIssue(
`/${layoutPath}/${index}`,
"must reference an existing item",
{ ref },
),
);
}
});
}
function semanticIssue(
instancePath: string,
message: string,
params: Record<string, unknown>,
): SemanticValidationIssue {
return {
instancePath,
schemaPath: "",
keyword: "semantic",
params,
message,
};
}
function escapeJsonPointer(value: string): string {
return value.replaceAll("~", "~0").replaceAll("/", "~1");
}

View file

@ -1,12 +0,0 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"declaration": true,
"emitDeclarationOnly": false,
"noEmit": false,
"outDir": "dist",
"rootDir": "src"
},
"include": ["src/**/*.ts"],
"exclude": ["dist", "node_modules", "src/**/*.test.ts"]
}

View file

@ -1,9 +0,0 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"baseUrl": ".",
"types": ["node", "bun-types"]
},
"include": ["src/**/*.ts"],
"exclude": ["dist", "node_modules"]
}

@ -1 +0,0 @@
Subproject commit a7a472083555152b8e1a2dc018d3be5b3b10d60b

View file

@ -1,362 +0,0 @@
#!/usr/bin/env bash
set -Eeuo pipefail
APP_NAME="${APP_NAME:-dimensionlab-website}"
SERVICE_NAME="${SERVICE_NAME:-dimensionlab-website.service}"
CONTAINER_NAME="${CONTAINER_NAME:-dimensionlab-website}"
IMAGE_REPO="${IMAGE_REPO:-localhost/dimensionlab-website}"
CONTAINERFILE="${CONTAINERFILE:-apps/web/Containerfile}"
PUBLIC_URL="${PUBLIC_URL:-https://dimensionlab.net/}"
TILE_BATCH_URL="${TILE_BATCH_URL:-https://dimensionlab.net/api/dashboard/tiles}"
DEPLOY_RESTART_STRATEGY="${DEPLOY_RESTART_STRATEGY:-auto}"
DEPLOY_SMOKE_TIMEOUT_SECONDS="${DEPLOY_SMOKE_TIMEOUT_SECONDS:-120}"
DEPLOY_CONTAINER_START_TIMEOUT_SECONDS="${DEPLOY_CONTAINER_START_TIMEOUT_SECONDS:-90}"
dry_run=false
rollback_tag=""
release_tag=""
latest_tag="${IMAGE_REPO}:latest"
container_cli=""
deployment_started=false
rollback_done=false
rollback_in_progress=false
usage() {
cat <<USAGE
Usage: $0 [--dry-run]
Build and deploy ${APP_NAME} for the production ${SERVICE_NAME} unit.
Options:
--dry-run Print the deployment actions without changing the host.
USAGE
}
log() {
printf '[deploy:%s] %s\n' "$APP_NAME" "$*"
}
fail() {
printf '[deploy:%s] ERROR: %s\n' "$APP_NAME" "$*" >&2
if [ "${deployment_started:-false}" = "true" ] && [ "${rollback_in_progress:-false}" != "true" ]; then
rollback || true
fi
exit 1
}
run() {
if "$dry_run"; then
printf '[deploy:%s] DRY-RUN:' "$APP_NAME"
printf ' %q' "$@"
printf '\n'
return 0
fi
"$@"
}
for arg in "$@"; do
case "$arg" in
--dry-run)
dry_run=true
;;
-h|--help)
usage
exit 0
;;
*)
usage >&2
fail "unknown argument: $arg"
;;
esac
done
deployment_ref() {
printf '%s' "${DEPLOY_REF:-${GITHUB_REF:-${FORGEJO_REF:-}}}"
}
deployment_event() {
printf '%s' "${DEPLOY_EVENT_NAME:-${GITHUB_EVENT_NAME:-${FORGEJO_EVENT_NAME:-}}}"
}
require_main_push() {
local event
local ref
event="$(deployment_event)"
ref="$(deployment_ref)"
if [ -n "$event" ] && [ "$event" != "push" ]; then
fail "refusing to deploy for event '$event'; production deploys only run for push"
fi
if [ -n "$ref" ]; then
[ "$ref" = "refs/heads/main" ] || fail "refusing to deploy ref '$ref'; expected refs/heads/main"
return 0
fi
local branch
branch="$(git branch --show-current 2>/dev/null || true)"
[ "$branch" = "main" ] || fail "refusing to deploy branch '$branch'; expected main"
}
select_container_cli() {
if [ -n "${DEPLOY_CONTAINER_CLI:-}" ]; then
command -v "$DEPLOY_CONTAINER_CLI" >/dev/null 2>&1 || fail "container CLI not found: $DEPLOY_CONTAINER_CLI"
container_cli="$DEPLOY_CONTAINER_CLI"
return 0
fi
if command -v podman >/dev/null 2>&1; then
container_cli="podman"
return 0
fi
if command -v docker >/dev/null 2>&1; then
container_cli="docker"
return 0
fi
fail "podman or docker is required"
}
current_sha() {
if [ -n "${DEPLOY_SHA:-${GITHUB_SHA:-}}" ]; then
printf '%s' "${DEPLOY_SHA:-${GITHUB_SHA:-}}"
return 0
fi
git rev-parse HEAD
}
tag_existing_latest_for_rollback() {
rollback_tag="${IMAGE_REPO}:rollback-$(date -u +%Y%m%d%H%M%S)"
if "$container_cli" image inspect "$latest_tag" >/dev/null 2>&1; then
log "tagging current latest image as $rollback_tag"
run "$container_cli" tag "$latest_tag" "$rollback_tag"
else
log "no existing $latest_tag image found; rollback image tag will not be created"
rollback_tag=""
fi
}
container_systemd_unit() {
"$container_cli" inspect "$CONTAINER_NAME" \
--format '{{ index .Config.Labels "PODMAN_SYSTEMD_UNIT" }}' 2>/dev/null || true
}
require_container_managed_by_service() {
local unit
if "$dry_run"; then
log "DRY-RUN: would require $CONTAINER_NAME to be managed by $SERVICE_NAME"
return 0
fi
unit="$(container_systemd_unit)"
[ "$unit" = "$SERVICE_NAME" ] || fail "refusing to stop $CONTAINER_NAME; expected PODMAN_SYSTEMD_UNIT=$SERVICE_NAME, got '${unit:-unset}'"
}
validate_restart_strategy() {
case "$DEPLOY_RESTART_STRATEGY" in
systemctl)
if ! "$dry_run" && ! systemctl --user show "$SERVICE_NAME" >/dev/null 2>&1; then
fail "systemctl --user cannot access $SERVICE_NAME"
fi
;;
quadlet-container|kill-container)
require_container_managed_by_service
;;
auto)
if "$dry_run"; then
log "DRY-RUN: would validate automatic restart strategy"
elif ! command -v systemctl >/dev/null 2>&1 || ! systemctl --user show "$SERVICE_NAME" >/dev/null 2>&1; then
require_container_managed_by_service
fi
;;
*)
fail "unknown DEPLOY_RESTART_STRATEGY: $DEPLOY_RESTART_STRATEGY"
;;
esac
}
initialize_submodules() {
log "initializing submodules"
run git config --global url."https://git.dimensionlab.net/".insteadOf "ssh://git@git.dimensionlab.net/"
run git submodule update --init --recursive
}
build_image() {
local sha
local short_sha
sha="$(current_sha)"
short_sha="${sha:0:12}"
release_tag="${IMAGE_REPO}:${short_sha}"
[ -f "$CONTAINERFILE" ] || fail "containerfile not found: $CONTAINERFILE"
log "building $release_tag and $latest_tag from $CONTAINERFILE"
run "$container_cli" build -f "$CONTAINERFILE" -t "$release_tag" -t "$latest_tag" .
}
restart_service() {
log "restarting $SERVICE_NAME with strategy $DEPLOY_RESTART_STRATEGY"
case "$DEPLOY_RESTART_STRATEGY" in
systemctl)
run systemctl --user restart "$SERVICE_NAME"
;;
quadlet-container|kill-container)
require_container_managed_by_service
run "$container_cli" stop "$CONTAINER_NAME"
;;
auto)
if command -v systemctl >/dev/null 2>&1 && systemctl --user is-active "$SERVICE_NAME" >/dev/null 2>&1; then
run systemctl --user restart "$SERVICE_NAME"
else
require_container_managed_by_service
run "$container_cli" stop "$CONTAINER_NAME"
fi
;;
*)
fail "unknown DEPLOY_RESTART_STRATEGY: $DEPLOY_RESTART_STRATEGY"
;;
esac
}
latest_image_id() {
"$container_cli" image inspect "$latest_tag" --format '{{.Id}}' 2>/dev/null || true
}
container_image_id() {
"$container_cli" inspect "$CONTAINER_NAME" --format '{{.Image}}' 2>/dev/null || true
}
container_running() {
local running
running="$("$container_cli" inspect "$CONTAINER_NAME" --format '{{.State.Running}}' 2>/dev/null || true)"
[ "$running" = "true" ]
}
wait_for_container_restart() {
local expected_image
if "$dry_run"; then
log "DRY-RUN: would wait for $CONTAINER_NAME to run $latest_tag"
return 0
fi
expected_image="$(latest_image_id)"
[ -n "$expected_image" ] || fail "could not resolve image id for $latest_tag"
wait_for_container_image "$expected_image" "new image" || fail "$CONTAINER_NAME did not restart on $latest_tag within ${DEPLOY_CONTAINER_START_TIMEOUT_SECONDS}s"
}
wait_for_container_image() {
local expected_image="$1"
local label="$2"
local deadline
deadline=$((SECONDS + DEPLOY_CONTAINER_START_TIMEOUT_SECONDS))
while [ "$SECONDS" -lt "$deadline" ]; do
if container_running && [ "$(container_image_id)" = "$expected_image" ]; then
log "$CONTAINER_NAME is running the $label"
return 0
fi
sleep 2
done
return 1
}
smoke_get() {
local url="$1"
curl -fsS --max-time 10 -o /dev/null "$url"
}
smoke_tiles() {
local response
response="$(
curl -fsS --max-time 20 \
-H "content-type: application/json" \
--data '{"tiles":[{"kind":"status","stripId":"footer-status","id":"system-status"}]}' \
"$TILE_BATCH_URL"
)"
[[ "$response" == *'"state":"ready"'* ]]
}
wait_for_smoke() {
local deadline
if "$dry_run"; then
log "DRY-RUN: would smoke check $PUBLIC_URL and $TILE_BATCH_URL"
return 0
fi
deadline=$((SECONDS + DEPLOY_SMOKE_TIMEOUT_SECONDS))
until smoke_get "$PUBLIC_URL" && smoke_tiles; do
if [ "$SECONDS" -ge "$deadline" ]; then
fail "smoke checks failed for $PUBLIC_URL and $TILE_BATCH_URL"
fi
sleep 3
done
log "smoke checks passed"
}
rollback() {
local rollback_image
if [ "$deployment_started" != "true" ] || [ -z "$rollback_tag" ] || [ "$rollback_done" = "true" ]; then
return 0
fi
rollback_done=true
rollback_in_progress=true
printf '[deploy:%s] rolling back to %s\n' "$APP_NAME" "$rollback_tag" >&2
rollback_image="$("$container_cli" image inspect "$rollback_tag" --format '{{.Id}}' 2>/dev/null || true)"
"$container_cli" tag "$rollback_tag" "$latest_tag" || true
if container_running; then
restart_service || true
else
printf '[deploy:%s] waiting for %s to recover with rollback image\n' "$APP_NAME" "$SERVICE_NAME" >&2
fi
if [ -n "$rollback_image" ] && wait_for_container_image "$rollback_image" "rollback image"; then
printf '[deploy:%s] rollback image is running\n' "$APP_NAME" >&2
else
printf '[deploy:%s] ERROR: rollback image did not become healthy\n' "$APP_NAME" >&2
fi
rollback_in_progress=false
}
on_error() {
local status=$?
rollback
exit "$status"
}
trap on_error ERR
require_main_push
select_container_cli
validate_restart_strategy
log "using container CLI: $container_cli"
log "target image: $latest_tag"
log "target service: $SERVICE_NAME"
initialize_submodules
tag_existing_latest_for_rollback
build_image
deployment_started=true
restart_service
wait_for_container_restart
wait_for_smoke
log "deployment finished"

30
src/app.css Normal file
View file

@ -0,0 +1,30 @@
: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;
}

11
src/app.html Normal file
View file

@ -0,0 +1,11 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
%sveltekit.head%
</head>
<body data-sveltekit-preload-data="hover">
<div style="display: contents">%sveltekit.body%</div>
</body>
</html>

View file

@ -0,0 +1,18 @@
export type PlaceholderStatus = "ready" | "building";
export interface PlaceholderDashboard {
title: string;
subtitle: string;
status: PlaceholderStatus;
message: string;
}
export function loadPlaceholderDashboard(): PlaceholderDashboard {
return {
title: "System Overview",
subtitle: "Dashboard runtime scaffold",
status: "building",
message:
"SvelteKit is running. Later issues will replace this placeholder with the validated dashboard model.",
};
}

View file

@ -0,0 +1,7 @@
<script lang="ts">
import "../app.css";
let { children } = $props();
</script>
{@render children()}

View file

@ -0,0 +1,7 @@
import { loadPlaceholderDashboard } from "$lib/server/dashboard";
export function load() {
return {
dashboard: loadPlaceholderDashboard(),
};
}

83
src/routes/+page.svelte Normal file
View file

@ -0,0 +1,83 @@
<script lang="ts">
import type { PageData } from "./$types";
let { data }: { data: PageData } = $props();
</script>
<svelte:head>
<title>{data.dashboard.title} | Dimension Lab</title>
<meta
name="description"
content="Standalone SvelteKit runtime for a model-driven system overview dashboard."
/>
</svelte:head>
<main class="shell" aria-labelledby="page-title">
<section class="hero">
<p class="eyebrow">{data.dashboard.subtitle}</p>
<h1 id="page-title">{data.dashboard.title}</h1>
<p class="message">{data.dashboard.message}</p>
<div class="status" data-state={data.dashboard.status}>
<span class="status__label">Runtime</span>
<strong>{data.dashboard.status}</strong>
</div>
</section>
</main>
<style>
.shell {
display: grid;
min-height: 100vh;
padding: clamp(1rem, 2vw, 2rem);
box-sizing: border-box;
}
.hero {
align-self: center;
max-width: 64rem;
border: 1px solid rgba(244, 244, 244, 0.24);
background: rgba(0, 0, 0, 0.72);
padding: clamp(1.25rem, 4vw, 3rem);
box-shadow: 0 0 0 1px rgba(207, 255, 0, 0.18) inset;
}
.eyebrow,
.status__label {
margin: 0;
color: #cfff00;
font-size: 0.78rem;
font-weight: 800;
letter-spacing: 0;
text-transform: uppercase;
}
h1 {
margin: 0.35rem 0 0;
font-size: clamp(2.6rem, 9vw, 7rem);
line-height: 0.9;
text-transform: uppercase;
}
.message {
max-width: 42rem;
margin: 1.25rem 0 0;
color: rgba(244, 244, 244, 0.72);
font-size: clamp(0.9rem, 2vw, 1.08rem);
line-height: 1.6;
}
.status {
display: inline-grid;
grid-template-columns: auto auto;
gap: 0.75rem;
align-items: center;
margin-top: 2rem;
border: 1px solid rgba(207, 255, 0, 0.6);
padding: 0.7rem 0.85rem;
text-transform: uppercase;
}
.status strong {
color: #f4f4f4;
}
</style>

12
svelte.config.js Normal file
View file

@ -0,0 +1,12 @@
import adapter from "@sveltejs/adapter-node";
import { vitePreprocess } from "@sveltejs/vite-plugin-svelte";
/** @type {import("@sveltejs/kit").Config} */
const config = {
preprocess: vitePreprocess(),
kit: {
adapter: adapter(),
},
};
export default config;

View file

@ -1,18 +0,0 @@
{
"compilerOptions": {
"allowJs": true,
"checkJs": true,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"ignoreDeprecations": "6.0",
"jsx": "react-jsx",
"module": "ESNext",
"moduleResolution": "bundler",
"resolveJsonModule": true,
"skipLibCheck": true,
"sourceMap": true,
"strict": true,
"target": "ES2022",
"types": ["node", "bun-types", "react", "react-dom"]
}
}

View file

@ -1,8 +1,14 @@
{
"files": [],
"references": [
{ "path": "./packages/dashboard-model" },
{ "path": "./apps/web" },
{ "path": "./packages/ui" }
]
"extends": "./.svelte-kit/tsconfig.json",
"compilerOptions": {
"allowJs": true,
"checkJs": true,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"skipLibCheck": true,
"sourceMap": true,
"strict": true,
"moduleResolution": "bundler"
}
}

View file

@ -1,98 +0,0 @@
{
"$schema": "https://turbo.build/schema.json",
"globalDependencies": [
"bun.lock",
"package.json",
"tsconfig.base.json",
"tsconfig.json"
],
"tasks": {
"build": {
"dependsOn": ["^build"],
"inputs": ["$TURBO_DEFAULT$", ".env*"],
"outputs": ["dist/**", "build/**"],
"env": ["NODE_ENV", "VITE_*"]
},
"check": {
"dependsOn": ["^build"],
"outputs": []
},
"test:unit": {
"dependsOn": ["^build"],
"outputs": [],
"env": [
"AGENT_CONFIG_TOKEN",
"DASHBOARD_MIGRATIONS_DIR",
"DATABASE_URL",
"DISABLE_LIVE_DATASOURCES",
"PROMETHEUS_BASE_URL"
]
},
"build-storybook": {
"dependsOn": ["^build"],
"inputs": ["$TURBO_DEFAULT$", ".env*"],
"outputs": ["storybook-static/**"],
"env": ["NODE_ENV", "STORYBOOK_*", "VITE_*"]
},
"test:e2e": {
"dependsOn": [
"build",
"^build",
"@dimensionlab/ui#build-storybook"
],
"outputs": ["test-results/**", "playwright-report/**"],
"env": [
"AGENT_CONFIG_TOKEN",
"CI",
"DASHBOARD_MIGRATIONS_DIR",
"DATABASE_URL",
"DISABLE_LIVE_DATASOURCES",
"PLAYWRIGHT_DATABASE_URL",
"PLAYWRIGHT_PORT",
"PLAYWRIGHT_STORYBOOK_PORT",
"PROMETHEUS_BASE_URL",
"STORYBOOK_STATIC_PORT"
]
},
"db:generate": {
"cache": false
},
"db:check": {
"outputs": [],
"env": ["DATABASE_URL"]
},
"dev": {
"dependsOn": ["^build"],
"cache": false,
"persistent": true,
"env": [
"AGENT_CONFIG_TOKEN",
"DASHBOARD_DEV_API_HOST",
"DASHBOARD_DEV_API_PORT",
"DASHBOARD_DEV_API_TARGET",
"DASHBOARD_MIGRATIONS_DIR",
"DATABASE_URL",
"HOST",
"PORT",
"PROMETHEUS_BASE_URL"
]
},
"preview": {
"cache": false,
"persistent": true,
"env": [
"AGENT_CONFIG_TOKEN",
"DASHBOARD_MIGRATIONS_DIR",
"DATABASE_URL",
"HOST",
"PORT",
"PROMETHEUS_BASE_URL"
]
},
"storybook": {
"cache": false,
"persistent": true,
"env": ["HOST", "PORT", "STORYBOOK_*", "VITE_*"]
}
}
}

6
vite.config.ts Normal file
View file

@ -0,0 +1,6 @@
import { sveltekit } from "@sveltejs/kit/vite";
import { defineConfig } from "vite";
export default defineConfig({
plugins: [sveltekit()],
});