Compare commits

...
Sign in to create a new pull request.

70 commits

Author SHA1 Message Date
a2c998b894 Merge pull request 'fix(web): remove prompt registry shortcut' (#57)
All checks were successful
Dimension Lab website / ci (push) Successful in 20s
Dimension Lab website / deploy (push) Successful in 33s
fix(web): remove prompt registry shortcut
2026-06-22 02:58:09 +02:00
vince
799299efca test(web): update dashboard e2e snapshots
All checks were successful
Dimension Lab website / ci (pull_request) Successful in 20s
Dimension Lab website / deploy (pull_request) Has been skipped
2026-06-22 02:57:13 +02:00
vince
6405254e42 fix(web): remove prompt registry shortcut
All checks were successful
Dimension Lab website / ci (pull_request) Successful in 20s
Dimension Lab website / deploy (pull_request) Has been skipped
2026-06-22 02:49:30 +02:00
8b0ef39cdf Merge pull request 'ci: use dedicated host deploy runner' (#56) from codex/use-host-deploy-runner into main
All checks were successful
Dimension Lab website / ci (push) Successful in 19s
Dimension Lab website / deploy (push) Successful in 33s
2026-06-20 17:41:46 +02:00
vince
d00a7b3b80 ci: use dedicated host deploy runner
All checks were successful
Dimension Lab website / ci (pull_request) Successful in 18s
Dimension Lab website / deploy (pull_request) Has been skipped
2026-06-20 17:37:38 +02:00
da9a5a942d Merge pull request 'docs(ci): document Forgejo deploy runner option' (#55)
Some checks failed
Dimension Lab website / ci (push) Successful in 18s
Dimension Lab website / deploy (push) Failing after 4s
2026-06-20 17:24:41 +02:00
vince
16c69a1065 docs(ci): document Forgejo deploy runner option
All checks were successful
Dimension Lab website / ci (pull_request) Successful in 20s
Dimension Lab website / deploy (pull_request) Has been skipped
2026-06-20 17:22:16 +02:00
b087e65978 Merge pull request 'ci: deploy website from Forgejo Actions' (#54)
Some checks failed
Dimension Lab website / ci (push) Successful in 20s
Dimension Lab website / deploy (push) Failing after 4s
2026-06-20 17:18:41 +02:00
vince
35cb1574b4 fix(ci): allow deploy job Podman socket access
All checks were successful
Dimension Lab website / ci (pull_request) Successful in 17s
Dimension Lab website / deploy (pull_request) Has been skipped
2026-06-20 17:12:29 +02:00
vince
d61d296ea1 fix(ci): verify deployment socket and rollback image
All checks were successful
Dimension Lab website / ci (pull_request) Successful in 19s
Dimension Lab website / deploy (pull_request) Has been skipped
2026-06-20 17:10:08 +02:00
vince
006c7e041f fix(ci): guard deploy auto fallback
All checks were successful
Dimension Lab website / ci (pull_request) Successful in 17s
Dimension Lab website / deploy (pull_request) Has been skipped
2026-06-20 16:59:36 +02:00
vince
a010520a94 fix(ci): run web unit tests with Bun
All checks were successful
Dimension Lab website / ci (pull_request) Successful in 17s
Dimension Lab website / deploy (pull_request) Has been skipped
2026-06-20 16:58:13 +02:00
vince
7e4d13a85c fix(ci): harden website deploy rollback
Some checks failed
Dimension Lab website / ci (pull_request) Failing after 15s
Dimension Lab website / deploy (pull_request) Has been skipped
2026-06-20 16:54:18 +02:00
vince
96fe4d26c7 ci: deploy website from Forgejo Actions
Some checks failed
Dimension Lab website / ci (pull_request) Failing after 35s
Dimension Lab website / deploy (pull_request) Has been skipped
2026-06-20 16:45:40 +02:00
29077923e8 Merge pull request 'fix(web): ignore stale aggregate health snapshots' (#53) 2026-06-20 15:54:33 +02:00
vince
3188d8b905 fix(web): ignore stale aggregate health snapshots 2026-06-20 15:49:22 +02:00
216545b838 Merge pull request 'perf(web): stream dashboard tile events' (#52)
Merge PR #52 from codex/dashboard-sse-events
2026-06-20 15:26:49 +02:00
vince
41fb964ebb perf(web): stream dashboard tile events 2026-06-20 15:23:14 +02:00
a66f1fdd57 Merge pull request 'perf(web): batch dashboard tile hydration' (#51)
Merge PR #51 from codex/dashboard-batch-tiles
2026-06-20 15:18:26 +02:00
vince
a7d0a4c3b2 fix(web): use structured dashboard tile match keys 2026-06-20 15:16:51 +02:00
vince
de87464ef1 perf(web): batch dashboard tile hydration 2026-06-20 15:12:18 +02:00
63b008095f Merge pull request 'perf(web): add dashboard hydration observability' (#50)
Merge PR #50 from codex/dashboard-observability
2026-06-20 15:06:55 +02:00
vince
0d27920a78 fix(web): preserve coalesced tile error logs 2026-06-20 15:04:51 +02:00
vince
328f6e00bf perf(web): add dashboard hydration observability 2026-06-20 14:58:25 +02:00
b6de67f081 Merge pull request 'perf(web): adapt dashboard refresh hydration' (#49)
Merge PR #49 from codex/dashboard-adaptive-hydration
2026-06-20 14:52:01 +02:00
vince
a071df3194 perf(web): adapt dashboard refresh hydration 2026-06-20 14:47:53 +02:00
30bff9da37 Merge pull request 'perf(web): restore last known dashboard tiles' (#48)
Merge PR #48 from codex/dashboard-last-known-tiles
2026-06-20 14:41:14 +02:00
vince
bac809952a fix(web): validate restored dashboard tile shapes 2026-06-20 14:38:19 +02:00
vince
88c448258d fix(web): ignore malformed dashboard tile snapshots 2026-06-20 14:32:09 +02:00
vince
4ce42fb52b perf(web): restore last known dashboard tiles 2026-06-20 14:25:43 +02:00
243d71686d Merge pull request 'perf(web): share service health snapshots' (#47)
Merge PR #47 from codex/dashboard-service-snapshot
2026-06-20 14:19:36 +02:00
vince
5e66367687 perf(web): share service health snapshots 2026-06-20 14:17:31 +02:00
9aa30b07db Merge pull request 'perf(web): cache dashboard tile responses' (#46)
Merge PR #46 from codex/dashboard-tile-cache
2026-06-20 14:09:57 +02:00
vince
bc1f64d566 perf(web): cache dashboard tile responses 2026-06-20 14:07:42 +02:00
cda40401ca Merge pull request 'perf(web): queue dashboard tile hydration' (#45)
Merge PR #45 from codex/dashboard-hydration-scheduler
2026-06-20 14:00:33 +02:00
vince
fd0559a143 perf(web): queue dashboard tile hydration 2026-06-20 13:59:02 +02:00
83c2c0f4a4 Merge pull request 'perf(web): pause dashboard refresh when inactive' (#44)
Merge PR #44 from codex/dashboard-client-lifecycle
2026-06-20 13:54:12 +02:00
vince
74d9aaf2b4 perf(web): pause dashboard refresh when inactive 2026-06-20 13:51:30 +02:00
b0b09eb8ad Merge pull request 'fix(web): skip install scripts in container deps stage' (#43) from codex/fix-website-container-build into main 2026-06-20 13:16:29 +02:00
vince
631e789546 fix(web): skip install scripts in container deps stage 2026-06-20 13:14:45 +02:00
9acaa18e6d Merge pull request 'feat(web): hydrate dashboard tiles independently' (#42) from codex/independent-tile-hydration into main 2026-06-20 12:38:54 +02:00
vince
2a1d94195e fix(web): scope dashboard tile hydration 2026-06-20 12:34:52 +02:00
vince
2963c5dfde Merge remote-tracking branch 'origin/main' into codex/independent-tile-hydration 2026-06-20 12:32:14 +02:00
73a15a2b8b Merge pull request #41 from codex/ui-submodule
refactor: move ui package to submodule
2026-06-20 12:24:59 +02:00
vince
23c8f0964c feat(web): hydrate dashboard tiles independently 2026-06-20 12:21:36 +02:00
vince
831b1b6640 fix: document ui submodule setup 2026-06-20 12:21:18 +02:00
vince
7a3a5b8df7 refactor: move ui package to submodule 2026-06-20 12:11:45 +02:00
021f2cf20e Merge pull request #40 from codex/turbo-env-inputs
fix(turbo): hash local env inputs
2026-06-20 07:52:40 +02:00
vince
24e7e8eda0 fix(turbo): hash local env inputs 2026-06-20 07:48:54 +02:00
3ff41e771b Merge pull request #39 from codex/ui-prop-type-exports
feat(ui): export component prop types
2026-06-20 07:41:51 +02:00
vince
5dccd70e0b test(ui): assert public prop exports are detected 2026-06-20 07:40:52 +02:00
vince
2535dccff2 feat(ui): export component prop types 2026-06-20 07:37:15 +02:00
7b96a7cb76 Merge pull request #38 from codex/turbo-ui-entrypoints
feat(ui): expose grouped component entrypoints
2026-06-20 07:34:36 +02:00
vince
ef43aac6df feat(ui): expose grouped component entrypoints 2026-06-20 07:31:35 +02:00
243d05cef7 Merge pull request #37 from codex/turbo-package-exports
refactor(turbo): consume workspace package exports
2026-06-20 07:27:51 +02:00
vince
2d779165cd fix(turbo): keep package exports fresh in dev 2026-06-20 07:26:29 +02:00
vince
a77826eab9 refactor(turbo): consume workspace package exports 2026-06-20 07:20:18 +02:00
9240ae4925 Merge pull request #36 from codex/turbo-pruned-container
build(container): use turbo pruned web workspace
2026-06-20 07:15:09 +02:00
vince
74e70cf714 fix(container): exclude generated files from pruned build 2026-06-20 07:12:46 +02:00
vince
3e46a2c0cc build(container): use turbo pruned web workspace 2026-06-20 07:01:15 +02:00
220fb55a47 Merge pull request #35 from codex/turbo-ui-filetree
refactor(ui): organize component source tree
2026-06-20 06:52:12 +02:00
vince
fe8d1e5642 test(ui): derive story inventory from component tree 2026-06-20 06:50:10 +02:00
vince
32ad2cebe4 refactor(ui): organize component source tree 2026-06-20 06:43:09 +02:00
8927f2ab8a Merge pull request #34 from codex/turbo-package-boundaries
refactor(model): extract dashboard model package
2026-06-20 06:36:31 +02:00
vince
9311b81102 fix(container): stage model package manifest 2026-06-20 06:34:31 +02:00
vince
7a6ad8ab0e refactor(model): extract dashboard model package 2026-06-20 06:26:19 +02:00
22f50d64f9 Merge pull request #33 from codex/turbo-idiomatic-workspace
build(turbo): normalize workspace task graph
2026-06-20 06:19:39 +02:00
vince
c8183bcc47 fix(turbo): rely on graph build before e2e 2026-06-20 06:16:41 +02:00
vince
ae6eabed6f build(turbo): normalize workspace task graph 2026-06-20 06:10:16 +02:00
4fc89c4d3f Merge pull request #32 from codex/turbo-component-library
refactor: split website and reusable UI package
2026-06-20 05:52:08 +02:00
147 changed files with 5521 additions and 4426 deletions

View file

@ -5,8 +5,18 @@ coverage
data data
dist dist
node_modules node_modules
out
playwright-report playwright-report
storybook-static storybook-static
test-results test-results
.env .env
.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

@ -0,0 +1,85 @@
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

1
.gitignore vendored
View file

@ -1,4 +1,5 @@
node_modules/ node_modules/
out/
.svelte-kit/ .svelte-kit/
build/ build/
dist/ dist/

4
.gitmodules vendored Normal file
View file

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

View file

@ -5,20 +5,26 @@ reusable React component library.
This project is not a Homepage customization and does not depend on Homepage This project is not a Homepage customization and does not depend on Homepage
runtime, frontend code, or configuration. The dashboard will be model-driven: runtime, frontend code, or configuration. The dashboard will be model-driven:
the reusable UI package stays content-free, while environment-specific data the reusable UI package stays content-free, the reusable dashboard model
lives in validated dashboard model state inside the web app. package owns schema and validation, and environment-specific data lives in
validated dashboard model state inside the web app.
## Workspace Layout ## Workspace Layout
- `apps/web`: Vite React website, Bun API server, model fixtures, Drizzle - `apps/web`: Vite React website, Bun API server, model fixtures, Drizzle
persistence, Playwright e2e checks, and container build. persistence, Playwright e2e checks, and container build.
- `packages/ui`: reusable dashboard React components, design tokens, - `packages/dashboard-model`: reusable dashboard schema, validation, and
shadcn/radix primitives, generic fixtures, and Storybook. 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. - `docs/superpowers`: migration specs and execution plans used for this repo.
## Development ## Development
```sh ```sh
git submodule update --init --recursive
bun install bun install
bun run dev bun run dev
``` ```
@ -32,7 +38,8 @@ This MVP uses Drizzle with Bun SQLite for local file-backed persistence.
- `bun run test`: run the unit test stage 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: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:e2e`: build and run Playwright browser smoke and QA checks.
- `bun run test:qa`: run the release gate through Turbo. - `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 build`: build the UI package, production website, and Bun server.
- `bun run preview`: preview the production web build. - `bun run preview`: preview the production web build.
- `bun run storybook`: start the UI package component explorer on port 6006. - `bun run storybook`: start the UI package component explorer on port 6006.
@ -64,7 +71,7 @@ with an explicit migration error.
## Seed Data ## Seed Data
The initial Dimension Lab dashboard lives in The initial Dimension Lab dashboard lives in
`apps/web/src/lib/model/fixtures/dimensionlab.ts` as validated model data. It `apps/web/src/lib/dashboard-seed/dimensionlab.ts` as validated model data. It
includes the first-screen telemetry, service groups, status strip, weather includes the first-screen telemetry, service groups, status strip, weather
module, Iconify icon identifiers, links, and datasource references. Values that 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 are not live yet are labeled as fallback values in the data so later datasource
@ -102,7 +109,9 @@ bun run test:qa
The gate runs TypeScript checks, Vitest coverage for model, The gate runs TypeScript checks, Vitest coverage for model,
persistence, renderer, datasource mocks, and presentation boundaries, the persistence, renderer, datasource mocks, and presentation boundaries, the
production build, the static Storybook build, and Playwright desktop/mobile production build, the static Storybook build, and Playwright desktop/mobile
smoke checks against the built adapter output. Playwright also performs 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, baseline screenshot checks, keyboard navigation checks, reduced-motion checks,
landmark checks, and axe accessibility checks against the real model-driven landmark checks, and axe accessibility checks against the real model-driven
route. route.
@ -120,6 +129,7 @@ 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: server entry at `apps/web/build/index.js`. A minimal deployment flow is:
```sh ```sh
git submodule update --init --recursive
bun install --frozen-lockfile bun install --frozen-lockfile
bun run build bun run build
cd apps/web cd apps/web
@ -132,10 +142,11 @@ the checked-in `apps/web/drizzle/` directory so startup migrations can run.
### Internal Container ### Internal Container
The checked-in `apps/web/Containerfile` builds the React client and Bun server The checked-in `apps/web/Containerfile` runs
from the workspace root into a runtime image. For the Dimension Lab internal `turbo prune @dimensionlab/web --docker`, installs the pruned manifest set, and
host, run it behind Caddy on a loopback port and mount persistent state at builds the React client plus Bun server from the pruned workspace source. For the
`/data`: Dimension Lab internal host, run it behind Caddy on a loopback port and mount
persistent state at `/data`:
```sh ```sh
podman build -f apps/web/Containerfile -t localhost/dimensionlab-website:latest . podman build -f apps/web/Containerfile -t localhost/dimensionlab-website:latest .
@ -150,3 +161,27 @@ The env file must provide `AGENT_CONFIG_TOKEN`. Runtime defaults inside the
image set `HOST=0.0.0.0`, `PORT=3000`, image set `HOST=0.0.0.0`, `PORT=3000`,
`DATABASE_URL=file:/data/dimensionlab.sqlite`, and `DATABASE_URL=file:/data/dimensionlab.sqlite`, and
`DASHBOARD_MIGRATIONS_DIR=/repo/apps/web/drizzle`. `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.

View file

@ -1,18 +1,25 @@
FROM docker.io/oven/bun:1.3.14 AS deps FROM docker.io/oven/bun:1.3.14 AS base
WORKDIR /repo WORKDIR /repo
ENV PATH=/repo/apps/web/node_modules/.bin:/repo/packages/ui/node_modules/.bin:/repo/node_modules/.bin:$PATH 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
COPY package.json bun.lock turbo.json tsconfig.base.json ./
COPY apps/web/package.json apps/web/package.json FROM base AS pruner
COPY packages/ui/package.json packages/ui/package.json
RUN bun install --frozen-lockfile 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 FROM deps AS build
COPY . . COPY --from=pruner /repo/out/full/ ./
RUN bun install --frozen-lockfile && bun run build COPY --from=pruner /repo/tsconfig.base.json /repo/tsconfig.json ./
RUN bun run build
FROM deps AS runtime FROM base AS runtime
WORKDIR /repo/apps/web WORKDIR /repo/apps/web
ENV NODE_ENV=production ENV NODE_ENV=production

View file

@ -5,17 +5,17 @@
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev": "bun src/server/dev.ts", "dev": "bun src/server/dev.ts",
"build": "vite build && bun build src/server/index.ts --target bun --outdir build", "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", "preview": "HOST=0.0.0.0 PORT=4173 bun build/index.js",
"check": "tsc --noEmit", "check": "tsc --noEmit",
"test": "vitest run", "test": "bun --bun vitest run",
"test:unit": "vitest run", "test:unit": "bun --bun vitest run",
"test:e2e": "env -u NO_COLOR playwright test", "test:e2e": "env -u NO_COLOR playwright test",
"test:qa": "bun run check && bun run test:unit && bun run build && bun run test:e2e",
"db:generate": "drizzle-kit generate", "db:generate": "drizzle-kit generate",
"db:check": "drizzle-kit check" "db:check": "drizzle-kit check"
}, },
"dependencies": { "dependencies": {
"@dimensionlab/dashboard-model": "workspace:*",
"@dimensionlab/ui": "workspace:*", "@dimensionlab/ui": "workspace:*",
"@sinclair/typebox": "^0.34.49", "@sinclair/typebox": "^0.34.49",
"ajv": "^8.20.0", "ajv": "^8.20.0",

View file

@ -21,13 +21,13 @@ export default defineConfig({
}, },
webServer: [ webServer: [
{ {
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`, command: `DISABLE_LIVE_DATASOURCES=1 DATABASE_URL=${databaseUrl} HOST=127.0.0.1 PORT=${port} bun build/index.js`,
url: baseURL, url: baseURL,
reuseExistingServer: false, reuseExistingServer: false,
timeout: 120_000, timeout: 120_000,
}, },
{ {
command: `cd ../.. && bun run build-storybook && STORYBOOK_STATIC_PORT=${storybookPort} bun apps/web/tests/e2e/storybook-server.ts`, command: `cd ../.. && STORYBOOK_STATIC_PORT=${storybookPort} bun apps/web/tests/e2e/storybook-server.ts`,
url: storybookURL, url: storybookURL,
reuseExistingServer: false, reuseExistingServer: false,
timeout: 120_000, timeout: 120_000,

View file

@ -1,6 +1,13 @@
import { renderToString } from "react-dom/server"; import { renderToString } from "react-dom/server";
import { describe, expect, test } from "vitest"; import { describe, expect, test } from "vitest";
import { AppStateView } from "./App"; 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", () => { describe("React app dashboard state view", () => {
test("renders loading dashboard state", () => { test("renders loading dashboard state", () => {
@ -41,4 +48,191 @@ describe("React app dashboard state view", () => {
expect(html).toContain("Dark"); expect(html).toContain("Dark");
expect(html).toContain("Light"); 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,5 +1,30 @@
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import type {
DashboardDocument,
DashboardModule,
ServiceEntry,
StatusItem,
TelemetryCard,
} from "@dimensionlab/dashboard-model";
import type { DashboardRuntimeState } from "$lib/server/dashboard"; 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 { dashboardDocumentToUiDashboard } from "$lib/ui-adapter/model-renderer";
import { import {
DashboardFrame, DashboardFrame,
@ -9,21 +34,46 @@ import {
resolveInitialUiTheme, resolveInitialUiTheme,
type UiTheme, type UiTheme,
type UiSeverity, type UiSeverity,
type UiDashboardPreview,
} from "@dimensionlab/ui"; } from "@dimensionlab/ui";
const loadingDashboardState: DashboardRuntimeState = { type DashboardTileResponse =
state: "loading", | {
title: "Loading Dashboard", state: "ready";
subtitle: "Fetching active model", tile: DashboardTileReference;
message: "Waiting for the active dashboard document.", 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({ export function AppStateView({
dashboard, dashboard,
onThemeChange, onThemeChange,
theme, theme,
hydratingItemIds,
}: { }: {
dashboard: DashboardRuntimeState; dashboard: DashboardRuntimeState;
hydratingItemIds?: ReadonlySet<string>;
onThemeChange?: (theme: UiTheme) => void; onThemeChange?: (theme: UiTheme) => void;
theme?: UiTheme; theme?: UiTheme;
}) { }) {
@ -33,9 +83,14 @@ export function AppStateView({
) : null; ) : null;
if (dashboard.state === "ready") { if (dashboard.state === "ready") {
const uiDashboard = markHydratingItems(
dashboardDocumentToUiDashboard(dashboard.document),
hydratingItemIds,
);
return ( return (
<DashboardFrame <DashboardFrame
dashboard={dashboardDocumentToUiDashboard(dashboard.document)} dashboard={uiDashboard}
actions={themeToggle} actions={themeToggle}
/> />
); );
@ -89,7 +144,10 @@ export function resolveDocumentMetadata(dashboard: DashboardRuntimeState): {
export default function App() { export default function App() {
const [dashboard, setDashboard] = const [dashboard, setDashboard] =
useState<DashboardRuntimeState>(loadingDashboardState); useState<DashboardRuntimeState | null>(null);
const [hydratingItemIds, setHydratingItemIds] = useState<Set<string>>(
() => new Set(),
);
const [theme, setTheme] = useState<UiTheme>(() => { const [theme, setTheme] = useState<UiTheme>(() => {
if (typeof window === "undefined") return "dark"; if (typeof window === "undefined") return "dark";
@ -97,6 +155,8 @@ export default function App() {
}); });
useEffect(() => { useEffect(() => {
if (!dashboard) return;
const metadata = resolveDocumentMetadata(dashboard); const metadata = resolveDocumentMetadata(dashboard);
document.title = metadata.title; document.title = metadata.title;
@ -119,47 +179,404 @@ export default function App() {
useEffect(() => { useEffect(() => {
let cancelled = false; let cancelled = false;
let refreshTimer: number | undefined; 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(),
);
async function loadDashboard() { function refreshPaused() {
const response = await fetch("/api/dashboard"); return shouldPauseDashboardRefresh({
const nextDashboard = (await response.json()) as DashboardRuntimeState; visibilityState: document.visibilityState,
online: navigator.onLine,
});
}
if (cancelled) return; function clearRefreshTimer() {
setDashboard(nextDashboard); if (refreshTimer !== undefined) {
window.clearTimeout(refreshTimer);
if (refreshTimer) {
window.clearInterval(refreshTimer);
refreshTimer = undefined; refreshTimer = undefined;
} }
}
const refreshIntervalSeconds = function scheduleNextRefresh(baseDelayMs: number) {
nextDashboard.state === "ready" clearRefreshTimer();
? nextDashboard.document.metadata.refreshIntervalSeconds if (cancelled || refreshPaused()) return;
: undefined;
if (refreshIntervalSeconds) { refreshTimer = window.setTimeout(() => {
refreshTimer = window.setInterval( refreshTimer = undefined;
() => void loadDashboard(), void loadDashboard();
refreshIntervalSeconds * 1000, }, 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(); void loadDashboard();
return () => { return () => {
cancelled = true; cancelled = true;
if (refreshTimer) window.clearInterval(refreshTimer); clearRefreshTimer();
unsubscribeTileEvents?.();
requestAborter.abortActiveRequests();
detachRefreshLifecycle();
}; };
}, []); }, []);
return ( return dashboard ? (
<AppStateView <AppStateView
dashboard={dashboard} dashboard={dashboard}
hydratingItemIds={hydratingItemIds}
theme={theme} theme={theme}
onThemeChange={setTheme} onThemeChange={setTheme}
/> />
); ) : null;
} }
function stateSeverity(state: DashboardRuntimeState["state"]): UiSeverity { function stateSeverity(state: DashboardRuntimeState["state"]): UiSeverity {
@ -181,3 +598,278 @@ function getThemeStorage(): Storage | undefined {
return undefined; 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

@ -0,0 +1,633 @@
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

@ -0,0 +1,886 @@
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

@ -7,7 +7,7 @@ import type {
DatasourceReference, DatasourceReference,
ServiceEntry, ServiceEntry,
TelemetryCard, TelemetryCard,
} from "../schema"; } from "@dimensionlab/dashboard-model";
describe("Dimension Lab dashboard seed", () => { describe("Dimension Lab dashboard seed", () => {
test("defines the primary first-screen sections as model data", () => { test("defines the primary first-screen sections as model data", () => {
@ -128,7 +128,6 @@ const verifiedSeedIconIds = new Set([
"mdi:pulse", "mdi:pulse",
"mdi:robot-outline", "mdi:robot-outline",
"mdi:router-network", "mdi:router-network",
"mdi:text-box-search",
"mdi:thermometer", "mdi:thermometer",
"mdi:web", "mdi:web",
"mdi:weather-sunny", "mdi:weather-sunny",

View file

@ -7,7 +7,7 @@ import {
type ServiceGroup, type ServiceGroup,
type Severity, type Severity,
type TelemetryCard, type TelemetryCard,
} from "../schema"; } from "@dimensionlab/dashboard-model";
type NumericValueKind = "percent" | "bytes" | "temperature" | "latency" | "number"; type NumericValueKind = "percent" | "bytes" | "temperature" | "latency" | "number";
@ -431,14 +431,6 @@ export const dimensionLabDashboardFixture: DashboardDocument = {
href: "https://models.dimensionlab.net", href: "https://models.dimensionlab.net",
datasource: uptimeMonitor(7), datasource: uptimeMonitor(7),
}), }),
service({
id: "prompt-registry",
label: "Prompt Registry",
description: "Shared prompts, traces, evals",
icon: "mdi:text-box-search",
href: "https://prompts.dimensionlab.net",
datasource: uptimeMonitor(20),
}),
]), ]),
group("systems", "Systems", [ group("systems", "Systems", [
service({ service({

View file

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

View file

@ -74,5 +74,7 @@ function findFiles(path: string, extension: string): string[] {
} }
function withoutInternalPackageScope(source: string): string { function withoutInternalPackageScope(source: string): string {
return source.replaceAll("@dimensionlab/ui", "@internal/ui"); return source
.replaceAll("@dimensionlab/dashboard-model", "@internal/dashboard-model")
.replaceAll("@dimensionlab/ui", "@internal/ui");
} }

View file

@ -3,8 +3,8 @@ import { mkdtemp } from "node:fs/promises";
import { tmpdir } from "node:os"; import { tmpdir } from "node:os";
import { join } from "node:path"; import { join } from "node:path";
import { afterEach, describe, expect, test } from "vitest"; import { afterEach, describe, expect, test } from "vitest";
import type { DashboardDocument } from "$lib/model"; import type { DashboardDocument } from "@dimensionlab/dashboard-model";
import { genericDashboardFixture } from "$lib/model/fixtures/generic"; import { genericDashboardFixture } from "@dimensionlab/dashboard-model/fixtures";
import { createDashboardStore, type DashboardStore } from "$lib/server/db/dashboard-store"; import { createDashboardStore, type DashboardStore } from "$lib/server/db/dashboard-store";
import { import {
AgentConfigAuthorizationError, AgentConfigAuthorizationError,

View file

@ -16,7 +16,7 @@ import {
type ServiceGroup, type ServiceGroup,
type StatusItem, type StatusItem,
type TelemetryCard, type TelemetryCard,
} from "$lib/model"; } from "@dimensionlab/dashboard-model";
import { import {
createDashboardStore, createDashboardStore,
DashboardRevisionNotFoundError, DashboardRevisionNotFoundError,

View file

@ -3,8 +3,8 @@ import { mkdtemp } from "node:fs/promises";
import { tmpdir } from "node:os"; import { tmpdir } from "node:os";
import { join } from "node:path"; import { join } from "node:path";
import { afterEach, describe, expect, test } from "vitest"; import { afterEach, describe, expect, test } from "vitest";
import { dimensionLabDashboardFixture } from "$lib/model/fixtures/dimensionlab"; import { dimensionLabDashboardFixture } from "$lib/dashboard-seed/dimensionlab";
import { genericDashboardFixture } from "$lib/model/fixtures/generic"; import { genericDashboardFixture } from "@dimensionlab/dashboard-model/fixtures";
import { createDashboardStore, type DashboardStore } from "./db/dashboard-store"; import { createDashboardStore, type DashboardStore } from "./db/dashboard-store";
import { loadDashboardRuntime } from "./dashboard"; import { loadDashboardRuntime } from "./dashboard";
@ -66,9 +66,6 @@ describe("dashboard runtime loader", () => {
expect(runtime.document.statusStrips[0]?.items.map((item) => item.id)).toContain( expect(runtime.document.statusStrips[0]?.items.map((item) => item.id)).toContain(
"auto-refresh", "auto-refresh",
); );
expect(runtime.document.serviceGroups.flatMap((group) => group.services).map((service) => service.id)).toContain(
"prompt-registry",
);
expect(store.listRevisions()).toHaveLength(2); expect(store.listRevisions()).toHaveLength(2);
expect(store.getActiveDashboard()?.revision.actor).toBe("initial-seed"); expect(store.getActiveDashboard()?.revision.actor).toBe("initial-seed");
}); });
@ -138,13 +135,5 @@ function olderDimensionLabSeed() {
...strip, ...strip,
items: strip.items.filter((item) => item.id !== "auto-refresh"), items: strip.items.filter((item) => item.id !== "auto-refresh"),
})); }));
document.serviceGroups = document.serviceGroups.map((group) =>
group.id === "ai-automation"
? {
...group,
services: group.services.filter((service) => service.id !== "prompt-registry"),
}
: group,
);
return document; return document;
} }

View file

@ -1,5 +1,5 @@
import { dimensionLabDashboardFixture } from "$lib/model/fixtures/dimensionlab"; import { dimensionLabDashboardFixture } from "$lib/dashboard-seed/dimensionlab";
import type { DashboardDocument } from "$lib/model"; import type { DashboardDocument } from "@dimensionlab/dashboard-model";
import { import {
createDashboardStore, createDashboardStore,
DashboardPersistenceValidationError, DashboardPersistenceValidationError,
@ -18,6 +18,9 @@ export interface DashboardRuntimeReady {
document: DashboardDocument; document: DashboardDocument;
schemaVersion: string; schemaVersion: string;
currentRevisionId: string; currentRevisionId: string;
liveDatasourceHydration?: {
enabled: boolean;
};
} }
export interface DashboardRuntimeEmpty { export interface DashboardRuntimeEmpty {

View file

@ -2,8 +2,8 @@ import { describe, expect, test, vi } from "vitest";
import { import {
DASHBOARD_SCHEMA_VERSION, DASHBOARD_SCHEMA_VERSION,
type DashboardDocument, type DashboardDocument,
} from "$lib/model"; } from "@dimensionlab/dashboard-model";
import { resolveDashboardDatasources } from "."; import { resolveDashboardDatasources, resolveDashboardTile } from ".";
describe("dashboard datasource resolution", () => { describe("dashboard datasource resolution", () => {
test("hydrates telemetry, service health, weather, and summary data from live adapters", async () => { test("hydrates telemetry, service health, weather, and summary data from live adapters", async () => {
@ -124,6 +124,100 @@ describe("dashboard datasource resolution", () => {
expect(resolved).not.toBe(testDocument); expect(resolved).not.toBe(testDocument);
expect(testDocument.telemetry[0].value.value).toBe(1); 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(); const testDocument = testDashboard();

View file

@ -8,10 +8,40 @@ import type {
StatusItem, StatusItem,
StatusStrip, StatusStrip,
TelemetryCard, TelemetryCard,
} from "$lib/model"; } 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 { export interface DatasourceResolutionOptions {
fetch?: DatasourceFetch; fetch?: DatasourceFetch;
now?: () => number;
prometheusBaseUrl?: string; prometheusBaseUrl?: string;
prometheusRangeSeconds?: number; prometheusRangeSeconds?: number;
prometheusStepSeconds?: number; prometheusStepSeconds?: number;
@ -46,16 +76,90 @@ export async function resolveDashboardDatasources(
}; };
} }
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 { interface DatasourceContext {
fetch: DatasourceFetch; fetch: DatasourceFetch;
fetchIdentity: number;
now: () => number;
prometheusBaseUrl: string; prometheusBaseUrl: string;
prometheusRangeSeconds: number; prometheusRangeSeconds: number;
prometheusStepSeconds: number; prometheusStepSeconds: number;
requestTimeoutMs: number; requestTimeoutMs: number;
serviceGroupsSnapshot?: Promise<ServiceGroup[]>;
} }
type DatasourceFetch = (input: string, init?: RequestInit) => Promise<Response>; 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 { interface PrometheusVectorResult {
metric?: Record<string, string>; metric?: Record<string, string>;
value?: [number, string]; value?: [number, string];
@ -67,8 +171,12 @@ interface PrometheusMatrixResult {
} }
function datasourceContext(options: DatasourceResolutionOptions): DatasourceContext { function datasourceContext(options: DatasourceResolutionOptions): DatasourceContext {
const fetch = options.fetch || globalThis.fetch;
return { return {
fetch: options.fetch || globalThis.fetch, fetch,
fetchIdentity: datasourceFetchIdentity(fetch),
now: options.now || Date.now,
prometheusBaseUrl: prometheusBaseUrl:
options.prometheusBaseUrl || options.prometheusBaseUrl ||
process.env.PROMETHEUS_BASE_URL || process.env.PROMETHEUS_BASE_URL ||
@ -79,6 +187,68 @@ function datasourceContext(options: DatasourceResolutionOptions): DatasourceCont
}; };
} }
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( async function resolveTelemetryCard(
card: TelemetryCard, card: TelemetryCard,
context: DatasourceContext, context: DatasourceContext,
@ -390,6 +560,66 @@ function resolveStatusItem(
return structuredClone(item); 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[]): { function serviceHealthSummary(serviceGroups: ServiceGroup[]): {
severity: Severity; severity: Severity;
value: string; value: string;
@ -673,3 +903,17 @@ function formatDuration(totalSeconds: number): string {
const minutes = Math.floor((seconds % 3_600) / 60); const minutes = Math.floor((seconds % 3_600) / 60);
return `${days}d ${hours}h ${minutes}m`; 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

@ -3,8 +3,8 @@ import { mkdtemp } from "node:fs/promises";
import { tmpdir } from "node:os"; import { tmpdir } from "node:os";
import { join } from "node:path"; import { join } from "node:path";
import { afterEach, describe, expect, test, vi } from "vitest"; import { afterEach, describe, expect, test, vi } from "vitest";
import { genericDashboardFixture } from "$lib/model/fixtures/generic"; import { genericDashboardFixture } from "@dimensionlab/dashboard-model/fixtures";
import type { DashboardDocument } from "$lib/model"; import type { DashboardDocument } from "@dimensionlab/dashboard-model";
import { import {
DashboardPersistenceValidationError, DashboardPersistenceValidationError,
createDashboardStore, createDashboardStore,

View file

@ -3,7 +3,7 @@ import { desc, eq } from "drizzle-orm";
import { import {
type DashboardDocument, type DashboardDocument,
type DashboardValidationFailure, type DashboardValidationFailure,
} from "$lib/model"; } from "@dimensionlab/dashboard-model";
import { import {
type DashboardDatabaseConnection, type DashboardDatabaseConnection,
openDashboardDatabase, openDashboardDatabase,

View file

@ -1,6 +1,6 @@
import { describe, expect, test } from "vitest"; import { describe, expect, test } from "vitest";
import { DASHBOARD_SCHEMA_VERSION } from "$lib/model"; import { DASHBOARD_SCHEMA_VERSION } from "@dimensionlab/dashboard-model";
import { genericDashboardFixture } from "$lib/model/fixtures/generic"; import { genericDashboardFixture } from "@dimensionlab/dashboard-model/fixtures";
import { import {
UnsupportedDashboardModelVersionError, UnsupportedDashboardModelVersionError,
migrateDashboardDocumentForPersistence, migrateDashboardDocumentForPersistence,

View file

@ -3,7 +3,7 @@ import {
validateDashboardDocument, validateDashboardDocument,
type DashboardDocument, type DashboardDocument,
type DashboardValidationFailure, type DashboardValidationFailure,
} from "$lib/model"; } from "@dimensionlab/dashboard-model";
export interface DashboardModelMigrationSuccess { export interface DashboardModelMigrationSuccess {
valid: true; valid: true;

View file

@ -1,4 +1,4 @@
import type { DashboardDocument } from "$lib/model"; import type { DashboardDocument } from "@dimensionlab/dashboard-model";
import { index, integer, sqliteTable, text } from "drizzle-orm/sqlite-core"; import { index, integer, sqliteTable, text } from "drizzle-orm/sqlite-core";
export const dashboardDocuments = sqliteTable("dashboard_documents", { export const dashboardDocuments = sqliteTable("dashboard_documents", {

View file

@ -1,9 +1,9 @@
import { readFileSync } from "node:fs"; import { readFileSync } from "node:fs";
import { join } from "node:path"; import { join } from "node:path";
import { describe, expect, test } from "vitest"; import { describe, expect, test } from "vitest";
import { type DashboardDocument } from "$lib/model"; import { type DashboardDocument } from "@dimensionlab/dashboard-model";
import { dimensionLabDashboardFixture } from "$lib/model/fixtures/dimensionlab"; import { dimensionLabDashboardFixture } from "$lib/dashboard-seed/dimensionlab";
import { genericDashboardFixture } from "$lib/model/fixtures/generic"; import { genericDashboardFixture } from "@dimensionlab/dashboard-model/fixtures";
import { dashboardDocumentToUiDashboard } from "./model-renderer"; import { dashboardDocumentToUiDashboard } from "./model-renderer";
const appRoot = process.cwd().endsWith(`${join("apps", "web")}`) const appRoot = process.cwd().endsWith(`${join("apps", "web")}`)
@ -93,6 +93,7 @@ describe("dashboard model renderer", () => {
"utf8", "utf8",
) )
.toLowerCase() .toLowerCase()
.replaceAll("@dimensionlab/dashboard-model", "@internal/dashboard-model")
.replaceAll("@dimensionlab/ui", "@internal/ui"); .replaceAll("@dimensionlab/ui", "@internal/ui");
expect(source).not.toContain("dimension"); expect(source).not.toContain("dimension");

View file

@ -6,7 +6,7 @@ import type {
StatusItem, StatusItem,
StatusStrip, StatusStrip,
TelemetryCard, TelemetryCard,
} from "$lib/model"; } from "@dimensionlab/dashboard-model";
import type { import type {
UiDashboardPreview, UiDashboardPreview,
UiModuleBlock, UiModuleBlock,

View file

@ -1,4 +1,6 @@
import { existsSync, readFileSync } from "node:fs"; 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 { join } from "node:path";
import { describe, expect, test } from "vitest"; import { describe, expect, test } from "vitest";
@ -18,10 +20,79 @@ describe("workspace boundaries", () => {
expect(packageJson.private).toBe(true); expect(packageJson.private).toBe(true);
expect(packageJson.workspaces).toEqual(["apps/*", "packages/*"]); expect(packageJson.workspaces).toEqual(["apps/*", "packages/*"]);
expect(packageJson.scripts?.build).toBe("turbo build"); expect(packageJson.scripts?.build).toBe("turbo run build");
expect(existsSync(join(root, "turbo.json"))).toBe(true); 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", () => { test("keeps the website app and reusable UI library as separate packages", () => {
const webPackage = JSON.parse( const webPackage = JSON.parse(
readFileSync(join(root, "apps/web/package.json"), "utf8"), readFileSync(join(root, "apps/web/package.json"), "utf8"),
@ -36,4 +107,472 @@ describe("workspace boundaries", () => {
expect(uiPackage.exports).toHaveProperty("."); expect(uiPackage.exports).toHaveProperty(".");
expect(uiPackage.exports).toHaveProperty("./styles.css"); 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,6 +1,6 @@
import { renderToString } from "react-dom/server"; import { renderToString } from "react-dom/server";
import { describe, expect, test } from "vitest"; import { describe, expect, test } from "vitest";
import { genericDashboardFixture } from "$lib/model/fixtures/generic"; import { genericDashboardFixture } from "@dimensionlab/dashboard-model/fixtures";
import { AppStateView, resolveDocumentMetadata } from "./App"; import { AppStateView, resolveDocumentMetadata } from "./App";
describe("home page model renderer", () => { describe("home page model renderer", () => {

View file

@ -2,6 +2,7 @@ import { readFileSync } from "node:fs";
import { join } from "node:path"; import { join } from "node:path";
import { describe, expect, test } from "vitest"; import { describe, expect, test } from "vitest";
import { createDevServerConfig } from "../../vite.config"; import { createDevServerConfig } from "../../vite.config";
import { apiServerArgs } from "./dev";
describe("local development runtime", () => { describe("local development runtime", () => {
test("starts the Bun API server together with the Vite dev server", () => { test("starts the Bun API server together with the Vite dev server", () => {
@ -22,4 +23,11 @@ describe("local development runtime", () => {
changeOrigin: true, changeOrigin: true,
}); });
}); });
test("uses development package export conditions for the Bun API server", () => {
expect(apiServerArgs).toEqual([
"--conditions=development",
"src/server/index.ts",
]);
});
}); });

View file

@ -3,6 +3,10 @@ const webPort = process.env.PORT || "5173";
const apiHost = process.env.DASHBOARD_DEV_API_HOST || "127.0.0.1"; const apiHost = process.env.DASHBOARD_DEV_API_HOST || "127.0.0.1";
const apiPort = process.env.DASHBOARD_DEV_API_PORT || "5174"; const apiPort = process.env.DASHBOARD_DEV_API_PORT || "5174";
const apiTarget = `http://${apiHost}:${apiPort}`; const apiTarget = `http://${apiHost}:${apiPort}`;
export const apiServerArgs = [
"--conditions=development",
"src/server/index.ts",
] as const;
if (import.meta.main) { if (import.meta.main) {
runDevServers(); runDevServers();
@ -51,7 +55,7 @@ export function runDevServers(): void {
process.on("SIGINT", () => shutdown(0)); process.on("SIGINT", () => shutdown(0));
process.on("SIGTERM", () => shutdown(0)); process.on("SIGTERM", () => shutdown(0));
spawn("api server", [process.execPath, "src/server/index.ts"], { spawn("api server", [process.execPath, ...apiServerArgs], {
HOST: apiHost, HOST: apiHost,
PORT: apiPort, PORT: apiPort,
}); });

View file

@ -0,0 +1,77 @@
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,6 +1,11 @@
import { extname, normalize } from "node:path"; import { extname, normalize } from "node:path";
import { handleAgentDashboardRoute } from "./routes/agent-dashboard"; import { handleAgentDashboardRoute } from "./routes/agent-dashboard";
import { handleDashboardRoute } from "./routes/dashboard"; import {
handleDashboardEventsRoute,
handleDashboardRoute,
handleDashboardTileRoute,
handleDashboardTilesRoute,
} from "./routes/dashboard";
const host = process.env.HOST || "0.0.0.0"; const host = process.env.HOST || "0.0.0.0";
const port = Number(process.env.PORT || 3000); const port = Number(process.env.PORT || 3000);
@ -23,6 +28,21 @@ export async function handleRequest(request: Request): Promise<Response> {
return handleDashboardRoute(); 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 (url.pathname === "/api/agent/dashboard") {
if (request.method !== "POST") return methodNotAllowed(["POST"]); if (request.method !== "POST") return methodNotAllowed(["POST"]);
return handleAgentDashboardRoute(request); return handleAgentDashboardRoute(request);

View file

@ -1,11 +1,34 @@
import { describe, expect, test } from "vitest"; import { afterAll, afterEach, describe, expect, test, vi } from "vitest";
import { dimensionLabDashboardFixture } from "$lib/model/fixtures/dimensionlab"; import { dimensionLabDashboardFixture } from "$lib/dashboard-seed/dimensionlab";
import { loadDashboardResponse } from "./dashboard"; import {
createDashboardTileCache,
dashboardTileCacheKey,
handleDashboardEventsRoute,
handleDashboardTilesRoute,
handleDashboardTileRoute,
loadDashboardResponse,
loadDashboardTilesResponse,
loadDashboardTileResponse,
type DashboardTileResolutionLogEvent,
} from "./dashboard";
describe("dashboard API route", () => { describe("dashboard API route", () => {
test("returns ready dashboard runtime state from the existing model loader", async () => { 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({ const response = await loadDashboardResponse({
disableLiveDatasources: true,
refreshSeedDocument: true, refreshSeedDocument: true,
seedIfEmpty: true, seedIfEmpty: true,
}); });
@ -15,5 +38,762 @@ describe("dashboard API route", () => {
expect(response.document.metadata.title).toBe( expect(response.document.metadata.title).toBe(
dimensionLabDashboardFixture.metadata.title, 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,21 +1,138 @@
import type { DashboardDocument } from "@dimensionlab/dashboard-model";
import { import {
loadDashboardRuntime, loadDashboardRuntime,
type DashboardRuntimeOptions, type DashboardRuntimeOptions,
type DashboardRuntimeState, type DashboardRuntimeState,
} from "$lib/server/dashboard"; } from "$lib/server/dashboard";
import { resolveDashboardDatasources } from "$lib/server/datasources"; import {
resolveDashboardDatasources,
resolveDashboardTile,
type DashboardTileReference,
type DashboardTileResolution,
type DatasourceResolutionOptions,
} from "$lib/server/datasources";
export interface LoadDashboardResponseOptions export interface LoadDashboardResponseOptions
extends Pick< extends Pick<
DashboardRuntimeOptions, DashboardRuntimeOptions,
"refreshSeedDocument" | "seedIfEmpty" | "seedDocument" "refreshSeedDocument" | "seedIfEmpty" | "seedDocument"
> { >,
DatasourceResolutionOptions {
disableLiveDatasources?: boolean; 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( export async function loadDashboardResponse(
options: LoadDashboardResponseOptions = {}, options: LoadDashboardResponseOptions = {},
): Promise<DashboardRuntimeState> { ): Promise<DashboardRuntimeState> {
const liveHydrationEnabled =
!options.disableLiveDatasources &&
process.env.DISABLE_LIVE_DATASOURCES !== "1";
const dashboard = loadDashboardRuntime(undefined, { const dashboard = loadDashboardRuntime(undefined, {
refreshSeedDocument: options.refreshSeedDocument ?? true, refreshSeedDocument: options.refreshSeedDocument ?? true,
seedIfEmpty: options.seedIfEmpty ?? true, seedIfEmpty: options.seedIfEmpty ?? true,
@ -26,16 +143,467 @@ export async function loadDashboardResponse(
return dashboard; return dashboard;
} }
if (options.disableLiveDatasources || process.env.DISABLE_LIVE_DATASOURCES === "1") { if (
return dashboard; !options.hydrateLiveDatasources ||
options.disableLiveDatasources ||
process.env.DISABLE_LIVE_DATASOURCES === "1"
) {
return {
...dashboard,
liveDatasourceHydration: {
enabled: liveHydrationEnabled,
},
};
} }
return { return {
...dashboard, ...dashboard,
document: await resolveDashboardDatasources(dashboard.document), 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> { export async function handleDashboardRoute(): Promise<Response> {
return Response.json(await loadDashboardResponse()); 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

@ -16,7 +16,6 @@ const linkedServiceIds = [
"open-webui", "open-webui",
"comfyui", "comfyui",
"models", "models",
"prompt-registry",
"adminer", "adminer",
"assistant", "assistant",
"suna", "suna",
@ -104,7 +103,7 @@ test.describe("dashboard page QA gate", () => {
expect(metrics.runtimeBottom).toBeLessThanOrEqual(956); expect(metrics.runtimeBottom).toBeLessThanOrEqual(956);
expect(metrics.footerBottom).toBeLessThanOrEqual(956); expect(metrics.footerBottom).toBeLessThanOrEqual(956);
expect(metrics.telemetryCardCount).toBe(16); expect(metrics.telemetryCardCount).toBe(16);
expect(metrics.serviceRowCount).toBe(28); expect(metrics.serviceRowCount).toBe(27);
expect(metrics.footerCellCount).toBe(5); expect(metrics.footerCellCount).toBe(5);
expect(metrics.clippedItems).toEqual([]); expect(metrics.clippedItems).toEqual([]);
}); });

Binary file not shown.

Before

Width:  |  Height:  |  Size: 304 KiB

After

Width:  |  Height:  |  Size: 301 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 454 KiB

After

Width:  |  Height:  |  Size: 451 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 309 KiB

After

Width:  |  Height:  |  Size: 302 KiB

Before After
Before After

View file

@ -3,8 +3,6 @@
"compilerOptions": { "compilerOptions": {
"baseUrl": ".", "baseUrl": ".",
"paths": { "paths": {
"@dimensionlab/ui": ["../../packages/ui/src/index.ts"],
"@dimensionlab/ui/styles.css": ["../../packages/ui/src/styles.css"],
"$lib/*": ["src/lib/*"] "$lib/*": ["src/lib/*"]
}, },
"types": ["node", "bun-types", "react", "react-dom", "vite/client"] "types": ["node", "bun-types", "react", "react-dom", "vite/client"]

View file

@ -24,12 +24,6 @@ export default defineConfig({
plugins: [react(), tailwindcss()], plugins: [react(), tailwindcss()],
resolve: { resolve: {
alias: { alias: {
"@dimensionlab/ui/styles.css": fileURLToPath(
new URL("../../packages/ui/src/styles.css", import.meta.url),
),
"@dimensionlab/ui": fileURLToPath(
new URL("../../packages/ui/src/index.ts", import.meta.url),
),
$lib: fileURLToPath(new URL("./src/lib", import.meta.url)), $lib: fileURLToPath(new URL("./src/lib", import.meta.url)),
}, },
}, },

View file

@ -12,6 +12,7 @@
"name": "@dimensionlab/web", "name": "@dimensionlab/web",
"version": "0.0.1", "version": "0.0.1",
"dependencies": { "dependencies": {
"@dimensionlab/dashboard-model": "workspace:*",
"@dimensionlab/ui": "workspace:*", "@dimensionlab/ui": "workspace:*",
"@sinclair/typebox": "^0.34.49", "@sinclair/typebox": "^0.34.49",
"ajv": "^8.20.0", "ajv": "^8.20.0",
@ -40,6 +41,22 @@
"vitest": "^4.1.9", "vitest": "^4.1.9",
}, },
}, },
"packages/dashboard-model": {
"name": "@dimensionlab/dashboard-model",
"version": "0.0.1",
"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",
},
},
"packages/ui": { "packages/ui": {
"name": "@dimensionlab/ui", "name": "@dimensionlab/ui",
"version": "0.0.1", "version": "0.0.1",
@ -137,6 +154,8 @@
"@babel/types": ["@babel/types@7.29.7", "", { "dependencies": { "@babel/helper-string-parser": "^7.29.7", "@babel/helper-validator-identifier": "^7.29.7" } }, "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA=="], "@babel/types": ["@babel/types@7.29.7", "", { "dependencies": { "@babel/helper-string-parser": "^7.29.7", "@babel/helper-validator-identifier": "^7.29.7" } }, "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA=="],
"@dimensionlab/dashboard-model": ["@dimensionlab/dashboard-model@workspace:packages/dashboard-model"],
"@dimensionlab/ui": ["@dimensionlab/ui@workspace:packages/ui"], "@dimensionlab/ui": ["@dimensionlab/ui@workspace:packages/ui"],
"@dimensionlab/web": ["@dimensionlab/web@workspace:apps/web"], "@dimensionlab/web": ["@dimensionlab/web@workspace:apps/web"],
@ -671,7 +690,7 @@
"@vitejs/plugin-react": ["@vitejs/plugin-react@6.0.2", "", { "dependencies": { "@rolldown/pluginutils": "^1.0.0" }, "peerDependencies": { "@rolldown/plugin-babel": "^0.1.7 || ^0.2.0", "babel-plugin-react-compiler": "^1.0.0", "vite": "^8.0.0" }, "optionalPeers": ["@rolldown/plugin-babel", "babel-plugin-react-compiler"] }, "sha512-DlSMqo4WhThw4vB8Mpn0Woe9J+Jfq1geJ61AKW0QEgLzGMNwtIMdxbDUzLxcun8W7NbJO0e2Jg/Nxm3cCSVzzg=="], "@vitejs/plugin-react": ["@vitejs/plugin-react@6.0.2", "", { "dependencies": { "@rolldown/pluginutils": "^1.0.0" }, "peerDependencies": { "@rolldown/plugin-babel": "^0.1.7 || ^0.2.0", "babel-plugin-react-compiler": "^1.0.0", "vite": "^8.0.0" }, "optionalPeers": ["@rolldown/plugin-babel", "babel-plugin-react-compiler"] }, "sha512-DlSMqo4WhThw4vB8Mpn0Woe9J+Jfq1geJ61AKW0QEgLzGMNwtIMdxbDUzLxcun8W7NbJO0e2Jg/Nxm3cCSVzzg=="],
"@vitest/expect": ["@vitest/expect@3.2.4", "", { "dependencies": { "@types/chai": "^5.2.2", "@vitest/spy": "3.2.4", "@vitest/utils": "3.2.4", "chai": "^5.2.0", "tinyrainbow": "^2.0.0" } }, "sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig=="], "@vitest/expect": ["@vitest/expect@4.1.9", "", { "dependencies": { "@standard-schema/spec": "^1.1.0", "@types/chai": "^5.2.2", "@vitest/spy": "4.1.9", "@vitest/utils": "4.1.9", "chai": "^6.2.2", "tinyrainbow": "^3.1.0" } }, "sha512-vl/rYsUKcBr3SnQn166+XR5ZQcgMx3DQhFWdfli/cWpLnLUmbxZvyrJZotLFUryib+LtArYMSTJ5RbQ57ZqrlA=="],
"@vitest/mocker": ["@vitest/mocker@4.1.9", "", { "dependencies": { "@vitest/spy": "4.1.9", "estree-walker": "^3.0.3", "magic-string": "^0.30.21" }, "peerDependencies": { "msw": "^2.4.9", "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" }, "optionalPeers": ["msw", "vite"] }, "sha512-EVkXzBjrPGM+cK8/ANWgBrkUCfJfb38/EfTSO8h7pWvKkyPkpWxvR7BkD2MyItMF62C97zAEoqdpUixwR/e+Rw=="], "@vitest/mocker": ["@vitest/mocker@4.1.9", "", { "dependencies": { "@vitest/spy": "4.1.9", "estree-walker": "^3.0.3", "magic-string": "^0.30.21" }, "peerDependencies": { "msw": "^2.4.9", "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" }, "optionalPeers": ["msw", "vite"] }, "sha512-EVkXzBjrPGM+cK8/ANWgBrkUCfJfb38/EfTSO8h7pWvKkyPkpWxvR7BkD2MyItMF62C97zAEoqdpUixwR/e+Rw=="],
@ -681,7 +700,7 @@
"@vitest/snapshot": ["@vitest/snapshot@4.1.9", "", { "dependencies": { "@vitest/pretty-format": "4.1.9", "@vitest/utils": "4.1.9", "magic-string": "^0.30.21", "pathe": "^2.0.3" } }, "sha512-Jc7RKGNBo8Z28WYIm0Niej4xdSPByRf6mU58VpHQkd6Zh05rlnA+twjbK5HyeIGHxrzsc3mJgS43uM0CZKzaIA=="], "@vitest/snapshot": ["@vitest/snapshot@4.1.9", "", { "dependencies": { "@vitest/pretty-format": "4.1.9", "@vitest/utils": "4.1.9", "magic-string": "^0.30.21", "pathe": "^2.0.3" } }, "sha512-Jc7RKGNBo8Z28WYIm0Niej4xdSPByRf6mU58VpHQkd6Zh05rlnA+twjbK5HyeIGHxrzsc3mJgS43uM0CZKzaIA=="],
"@vitest/spy": ["@vitest/spy@3.2.4", "", { "dependencies": { "tinyspy": "^4.0.3" } }, "sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw=="], "@vitest/spy": ["@vitest/spy@4.1.9", "", {}, "sha512-fHpsS6mIi+PiEW+vcRVOMkX1oSaPKne3VOclSFICPcGOmfKgXPU5iAah+wcNcj2xPrCCmfq99IDGf+EojhhvhA=="],
"@vitest/utils": ["@vitest/utils@4.1.9", "", { "dependencies": { "@vitest/pretty-format": "4.1.9", "convert-source-map": "^2.0.0", "tinyrainbow": "^3.1.0" } }, "sha512-A51o8ymO5PpqlWNnBP9ZHPXDIpuMtTLlGSjN7la4US+LJzoUMyhwjA5QXlm39JexgwHKW4Xjs8Z2d3dLCXOeuA=="], "@vitest/utils": ["@vitest/utils@4.1.9", "", { "dependencies": { "@vitest/pretty-format": "4.1.9", "convert-source-map": "^2.0.0", "tinyrainbow": "^3.1.0" } }, "sha512-A51o8ymO5PpqlWNnBP9ZHPXDIpuMtTLlGSjN7la4US+LJzoUMyhwjA5QXlm39JexgwHKW4Xjs8Z2d3dLCXOeuA=="],
@ -745,7 +764,7 @@
"caniuse-lite": ["caniuse-lite@1.0.30001799", "", {}, "sha512-hG1bReV+OUU+MOqK4t/ZWI0tZOyz3rqS9XuhOUz1cIcbwBKjOyJEJuw9ER5JuNyqxNk8u/JUVbGibBOL1yrjFw=="], "caniuse-lite": ["caniuse-lite@1.0.30001799", "", {}, "sha512-hG1bReV+OUU+MOqK4t/ZWI0tZOyz3rqS9XuhOUz1cIcbwBKjOyJEJuw9ER5JuNyqxNk8u/JUVbGibBOL1yrjFw=="],
"chai": ["chai@5.3.3", "", { "dependencies": { "assertion-error": "^2.0.1", "check-error": "^2.1.1", "deep-eql": "^5.0.1", "loupe": "^3.1.0", "pathval": "^2.0.0" } }, "sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw=="], "chai": ["chai@6.2.2", "", {}, "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg=="],
"chalk": ["chalk@5.6.2", "", {}, "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA=="], "chalk": ["chalk@5.6.2", "", {}, "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA=="],
@ -873,7 +892,7 @@
"esprima": ["esprima@4.0.1", "", { "bin": { "esparse": "./bin/esparse.js", "esvalidate": "./bin/esvalidate.js" } }, "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A=="], "esprima": ["esprima@4.0.1", "", { "bin": { "esparse": "./bin/esparse.js", "esvalidate": "./bin/esvalidate.js" } }, "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A=="],
"estree-walker": ["estree-walker@2.0.2", "", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="], "estree-walker": ["estree-walker@3.0.3", "", { "dependencies": { "@types/estree": "^1.0.0" } }, "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g=="],
"esutils": ["esutils@2.0.3", "", {}, "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="], "esutils": ["esutils@2.0.3", "", {}, "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="],
@ -1479,6 +1498,8 @@
"@rolldown/binding-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.10.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA=="], "@rolldown/binding-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.10.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA=="],
"@rollup/pluginutils/estree-walker": ["estree-walker@2.0.2", "", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="],
"@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.11.0", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.2", "tslib": "^2.4.0" }, "bundled": true }, "sha512-l9Oo58x0HOP5znGzVhYW9U3e5wVuA4LAZU2AGezTmkhO1CgQRFDhDg4nneHsu/t3WniXg9QrG2nIXL/ZS8ln8Q=="], "@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.11.0", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.2", "tslib": "^2.4.0" }, "bundled": true }, "sha512-l9Oo58x0HOP5znGzVhYW9U3e5wVuA4LAZU2AGezTmkhO1CgQRFDhDg4nneHsu/t3WniXg9QrG2nIXL/ZS8ln8Q=="],
"@tailwindcss/oxide-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.11.0", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-55coeOFKHv1ywEcUXJtWU5f+Jr/W5tZDvZig8DLKSwUN1JpROQ4rk/SNOQiFWmaR/VKF4zuFyW1B8JduOSv6Pg=="], "@tailwindcss/oxide-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.11.0", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-55coeOFKHv1ywEcUXJtWU5f+Jr/W5tZDvZig8DLKSwUN1JpROQ4rk/SNOQiFWmaR/VKF4zuFyW1B8JduOSv6Pg=="],
@ -1495,14 +1516,6 @@
"@testing-library/dom/dom-accessibility-api": ["dom-accessibility-api@0.5.16", "", {}, "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg=="], "@testing-library/dom/dom-accessibility-api": ["dom-accessibility-api@0.5.16", "", {}, "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg=="],
"@vitest/expect/@vitest/utils": ["@vitest/utils@3.2.4", "", { "dependencies": { "@vitest/pretty-format": "3.2.4", "loupe": "^3.1.4", "tinyrainbow": "^2.0.0" } }, "sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA=="],
"@vitest/expect/tinyrainbow": ["tinyrainbow@2.0.0", "", {}, "sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw=="],
"@vitest/mocker/@vitest/spy": ["@vitest/spy@4.1.9", "", {}, "sha512-fHpsS6mIi+PiEW+vcRVOMkX1oSaPKne3VOclSFICPcGOmfKgXPU5iAah+wcNcj2xPrCCmfq99IDGf+EojhhvhA=="],
"@vitest/mocker/estree-walker": ["estree-walker@3.0.3", "", { "dependencies": { "@types/estree": "^1.0.0" } }, "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g=="],
"body-parser/content-type": ["content-type@2.0.0", "", {}, "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ=="], "body-parser/content-type": ["content-type@2.0.0", "", {}, "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ=="],
"cliui/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], "cliui/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
@ -1549,14 +1562,14 @@
"shadcn/open": ["open@11.0.0", "", { "dependencies": { "default-browser": "^5.4.0", "define-lazy-prop": "^3.0.0", "is-in-ssh": "^1.0.0", "is-inside-container": "^1.0.0", "powershell-utils": "^0.1.0", "wsl-utils": "^0.3.0" } }, "sha512-smsWv2LzFjP03xmvFoJ331ss6h+jixfA4UUV/Bsiyuu4YJPfN+FIQGOIiv4w9/+MoHkfkJ22UIaQWRVFRfH6Vw=="], "shadcn/open": ["open@11.0.0", "", { "dependencies": { "default-browser": "^5.4.0", "define-lazy-prop": "^3.0.0", "is-in-ssh": "^1.0.0", "is-inside-container": "^1.0.0", "powershell-utils": "^0.1.0", "wsl-utils": "^0.3.0" } }, "sha512-smsWv2LzFjP03xmvFoJ331ss6h+jixfA4UUV/Bsiyuu4YJPfN+FIQGOIiv4w9/+MoHkfkJ22UIaQWRVFRfH6Vw=="],
"storybook/@vitest/expect": ["@vitest/expect@3.2.4", "", { "dependencies": { "@types/chai": "^5.2.2", "@vitest/spy": "3.2.4", "@vitest/utils": "3.2.4", "chai": "^5.2.0", "tinyrainbow": "^2.0.0" } }, "sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig=="],
"storybook/@vitest/spy": ["@vitest/spy@3.2.4", "", { "dependencies": { "tinyspy": "^4.0.3" } }, "sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw=="],
"string-width/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], "string-width/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
"type-is/content-type": ["content-type@2.0.0", "", {}, "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ=="], "type-is/content-type": ["content-type@2.0.0", "", {}, "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ=="],
"vitest/@vitest/expect": ["@vitest/expect@4.1.9", "", { "dependencies": { "@standard-schema/spec": "^1.1.0", "@types/chai": "^5.2.2", "@vitest/spy": "4.1.9", "@vitest/utils": "4.1.9", "chai": "^6.2.2", "tinyrainbow": "^3.1.0" } }, "sha512-vl/rYsUKcBr3SnQn166+XR5ZQcgMx3DQhFWdfli/cWpLnLUmbxZvyrJZotLFUryib+LtArYMSTJ5RbQ57ZqrlA=="],
"vitest/@vitest/spy": ["@vitest/spy@4.1.9", "", {}, "sha512-fHpsS6mIi+PiEW+vcRVOMkX1oSaPKne3VOclSFICPcGOmfKgXPU5iAah+wcNcj2xPrCCmfq99IDGf+EojhhvhA=="],
"wrap-ansi/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], "wrap-ansi/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="],
"wrap-ansi/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], "wrap-ansi/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
@ -1625,8 +1638,6 @@
"@oxc-resolver/binding-wasm32-wasi/@emnapi/core/@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.2.2", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA=="], "@oxc-resolver/binding-wasm32-wasi/@emnapi/core/@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.2.2", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA=="],
"@vitest/expect/@vitest/utils/@vitest/pretty-format": ["@vitest/pretty-format@3.2.4", "", { "dependencies": { "tinyrainbow": "^2.0.0" } }, "sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA=="],
"cliui/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], "cliui/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
"cross-spawn/which/isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="], "cross-spawn/which/isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="],
@ -1689,10 +1700,16 @@
"shadcn/open/wsl-utils": ["wsl-utils@0.3.1", "", { "dependencies": { "is-wsl": "^3.1.0", "powershell-utils": "^0.1.0" } }, "sha512-g/eziiSUNBSsdDJtCLB8bdYEUMj4jR7AGeUo96p/3dTafgjHhpF4RiCFPiRILwjQoDXx5MqkBr4fwWtR3Ky4Wg=="], "shadcn/open/wsl-utils": ["wsl-utils@0.3.1", "", { "dependencies": { "is-wsl": "^3.1.0", "powershell-utils": "^0.1.0" } }, "sha512-g/eziiSUNBSsdDJtCLB8bdYEUMj4jR7AGeUo96p/3dTafgjHhpF4RiCFPiRILwjQoDXx5MqkBr4fwWtR3Ky4Wg=="],
"storybook/@vitest/expect/@vitest/utils": ["@vitest/utils@3.2.4", "", { "dependencies": { "@vitest/pretty-format": "3.2.4", "loupe": "^3.1.4", "tinyrainbow": "^2.0.0" } }, "sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA=="],
"storybook/@vitest/expect/chai": ["chai@5.3.3", "", { "dependencies": { "assertion-error": "^2.0.1", "check-error": "^2.1.1", "deep-eql": "^5.0.1", "loupe": "^3.1.0", "pathval": "^2.0.0" } }, "sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw=="],
"storybook/@vitest/expect/tinyrainbow": ["tinyrainbow@2.0.0", "", {}, "sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw=="],
"string-width/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], "string-width/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
"vitest/@vitest/expect/chai": ["chai@6.2.2", "", {}, "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg=="],
"wrap-ansi/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], "wrap-ansi/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
"storybook/@vitest/expect/@vitest/utils/@vitest/pretty-format": ["@vitest/pretty-format@3.2.4", "", { "dependencies": { "tinyrainbow": "^2.0.0" } }, "sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA=="],
} }
} }

View file

@ -9,18 +9,18 @@
"packages/*" "packages/*"
], ],
"scripts": { "scripts": {
"dev": "turbo dev --filter=@dimensionlab/web", "dev": "turbo run dev --filter=@dimensionlab/web",
"build": "turbo build", "build": "turbo run build",
"preview": "bun run --cwd apps/web preview", "preview": "turbo run preview --filter=@dimensionlab/web",
"storybook": "turbo storybook --filter=@dimensionlab/ui", "storybook": "turbo run storybook --filter=@dimensionlab/ui",
"build-storybook": "turbo build-storybook --filter=@dimensionlab/ui", "build-storybook": "turbo run build-storybook --filter=@dimensionlab/ui",
"check": "turbo check", "check": "turbo run check",
"test": "turbo test:unit", "test": "turbo run test:unit",
"test:unit": "turbo test:unit", "test:unit": "turbo run test:unit",
"test:e2e": "turbo test:e2e --filter=@dimensionlab/web", "test:e2e": "turbo run test:e2e --filter=@dimensionlab/web",
"test:qa": "turbo test:qa", "test:qa": "turbo run check test:unit build @dimensionlab/ui#build-storybook @dimensionlab/web#test:e2e",
"db:generate": "bun run --cwd apps/web db:generate", "db:generate": "turbo run db:generate --filter=@dimensionlab/web",
"db:check": "bun run --cwd apps/web db:check" "db:check": "turbo run db:check --filter=@dimensionlab/web"
}, },
"devDependencies": { "devDependencies": {
"turbo": "^2.5.0" "turbo": "^2.5.0"

View file

@ -0,0 +1,36 @@
{
"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

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

View file

@ -5,7 +5,6 @@ import {
validateDashboardDocument, validateDashboardDocument,
} from "."; } from ".";
import { import {
dimensionLabDashboardFixture,
genericDashboardFixture, genericDashboardFixture,
} from "./fixtures"; } from "./fixtures";
@ -19,12 +18,6 @@ describe("dashboard model validation", () => {
} }
}); });
it("accepts the Dimension Lab dashboard fixture", () => {
const result = validateDashboardDocument(dimensionLabDashboardFixture);
expect(result.valid).toBe(true);
});
it("rejects documents with an unsupported schema version", () => { it("rejects documents with an unsupported schema version", () => {
const invalid = { const invalid = {
...genericDashboardFixture, ...genericDashboardFixture,

View file

@ -0,0 +1,12 @@
{
"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

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

1
packages/ui Submodule

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

View file

@ -1,15 +0,0 @@
import type { StorybookConfig } from "@storybook/react-vite";
const config: StorybookConfig = {
stories: ["../src/**/*.stories.@(js|ts|tsx)"],
addons: [
"@storybook/addon-a11y",
"@storybook/addon-vitest",
],
framework: {
name: "@storybook/react-vite",
options: {},
},
};
export default config;

View file

@ -1,51 +0,0 @@
import "../src/styles.css";
import type { Preview } from "@storybook/react-vite";
const preview: Preview = {
decorators: [
(Story, context) => {
const theme = context.globals.theme === "light" ? "light" : "dark";
if (typeof document !== "undefined") {
document.documentElement.setAttribute("data-ui-theme", theme);
}
return Story();
},
],
globalTypes: {
theme: {
description: "Dashboard component theme",
defaultValue: "dark",
toolbar: {
title: "Theme",
icon: "circlehollow",
items: [
{ value: "dark", title: "Dark" },
{ value: "light", title: "Light" },
],
dynamicTitle: true,
},
},
},
parameters: {
backgrounds: {
default: "canvas",
values: [
{ name: "canvas", value: "#0b0f0d" },
{ name: "raised", value: "#151d18" },
{ name: "light canvas", value: "#eef2e7" },
{ name: "light raised", value: "#f1f5ea" },
],
},
controls: {
matchers: {
color: /(background|color)$/i,
date: /Date$/i,
},
},
layout: "fullscreen",
},
};
export default preview;

View file

@ -1,25 +0,0 @@
{
"$schema": "https://ui.shadcn.com/schema.json",
"style": "radix-nova",
"rsc": false,
"tsx": true,
"tailwind": {
"config": "",
"css": "src/styles.css",
"baseColor": "neutral",
"cssVariables": true,
"prefix": ""
},
"iconLibrary": "lucide",
"rtl": false,
"aliases": {
"components": "src/components",
"utils": "src/utils",
"ui": "src/primitives",
"lib": "src",
"hooks": "src/hooks"
},
"menuColor": "default",
"menuAccent": "subtle",
"registries": {}
}

View file

@ -1,52 +0,0 @@
{
"name": "@dimensionlab/ui",
"version": "0.0.1",
"private": true,
"type": "module",
"sideEffects": ["*.css", "**/*.css"],
"exports": {
".": {
"types": "./dist/index.d.ts",
"default": "./dist/index.js"
},
"./styles.css": "./dist/styles.css",
"./tokens.css": "./dist/tokens.css"
},
"scripts": {
"build": "rm -rf dist && tsc -p tsconfig.build.json && mkdir -p dist/components && cp src/styles.css dist/styles.css && cp src/tokens.css dist/tokens.css && cp src/components/styles.css dist/components/styles.css",
"check": "tsc --noEmit",
"test": "vitest run",
"test:unit": "vitest run",
"storybook": "storybook dev -p 6006 --host 0.0.0.0",
"build-storybook": "storybook build"
},
"dependencies": {
"@fontsource-variable/geist": "^5.2.9",
"@iconify/react": "^6.0.2",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"lucide-react": "^1.21.0",
"radix-ui": "^1.6.0",
"tailwind-merge": "^3.6.0",
"tw-animate-css": "^1.4.0",
"uplot": "^1.6.32"
},
"peerDependencies": {
"react": "^19.0.0",
"react-dom": "^19.0.0"
},
"devDependencies": {
"@storybook/addon-a11y": "^10.4.6",
"@storybook/addon-vitest": "^10.4.6",
"@storybook/react-vite": "^10.4.6",
"@types/bun": "^1.3.14",
"@types/node": "^25.9.3",
"@types/react": "^19.2.17",
"@types/react-dom": "^19.2.3",
"bun-types": "^1.3.14",
"storybook": "^10.4.6",
"typescript": "^6.0.3",
"vite": "^8.0.16",
"vitest": "^4.1.9"
}
}

View file

@ -1,11 +0,0 @@
import type { UiSeverity } from "../types";
import { StatusBadge } from "./StatusBadge";
export interface BadgeProps {
label: string;
severity?: UiSeverity;
}
export function Badge({ label, severity = "neutral" }: BadgeProps) {
return <StatusBadge label={label} severity={severity} />;
}

View file

@ -1,39 +0,0 @@
import type { ButtonHTMLAttributes } from "react";
import { IconGlyph } from "./IconGlyph";
export interface ButtonProps
extends Omit<ButtonHTMLAttributes<HTMLButtonElement>, "type"> {
label: string;
variant?: "primary" | "secondary" | "danger" | "ghost";
size?: "default" | "compact";
icon?: string;
loading?: boolean;
type?: "button" | "submit" | "reset";
}
export function Button({
label,
variant = "primary",
size = "default",
icon,
disabled = false,
loading = false,
type = "button",
className = "",
...buttonProps
}: ButtonProps) {
return (
<button
{...buttonProps}
className={className ? `ui-button ${className} ` : "ui-button "}
data-variant={variant}
data-size={size}
data-loading={loading}
disabled={disabled}
type={type}
>
{icon ? <IconGlyph name={icon} size="sm" /> : null}
<span>{loading ? "Loading" : label}</span>
</button>
);
}

View file

@ -1,26 +0,0 @@
export interface CornerBracketFrameProps {
density?: "regular" | "tight";
size?: "sm" | "md" | "lg";
tone?: "neutral" | "accent" | "danger";
}
export function CornerBracketFrame({
density = "regular",
size = "md",
tone = "neutral",
}: CornerBracketFrameProps) {
return (
<div
className="corner-bracket-frame"
data-density={density}
data-size={size}
data-tone={tone}
aria-hidden="true"
>
<span data-corner="top-left" />
<span data-corner="top-right" />
<span data-corner="bottom-left" />
<span data-corner="bottom-right" />
</div>
);
}

View file

@ -1,59 +0,0 @@
import { useId } from "react";
import type { ReactNode } from "react";
import type { UiDashboardPreview } from "../types";
import { ModuleCard } from "./ModuleCard";
import { ServicePanel } from "./ServicePanel";
import { StatusStrip } from "./StatusStrip";
import { TelemetryGrid } from "./TelemetryGrid";
export interface DashboardFrameProps {
actions?: ReactNode;
dashboard: UiDashboardPreview;
titleId?: string;
}
export function DashboardFrame({
actions,
dashboard,
titleId,
}: DashboardFrameProps) {
const generatedTitleId = useId();
const resolvedTitleId = titleId || `${generatedTitleId}-title`;
return (
<main className="dashboard-frame" aria-labelledby={resolvedTitleId}>
<header className="dashboard-frame__header console-header">
<div className="dashboard-frame__title">
<div className="dashboard-frame__title-copy">
{dashboard.eyebrow ? <p>{dashboard.eyebrow}</p> : null}
<h1 id={resolvedTitleId}>{dashboard.title}</h1>
{dashboard.subtitle ? <span>{dashboard.subtitle}</span> : null}
</div>
{actions ? (
<div className="dashboard-frame__actions">{actions}</div>
) : null}
</div>
{dashboard.modules.length ? (
<div className="dashboard-frame__modules">
{dashboard.modules.map((module) => (
<ModuleCard key={module.id} module={module} />
))}
</div>
) : null}
</header>
<TelemetryGrid cards={dashboard.telemetry} />
<section className="dashboard-frame__panels" aria-label="Service groups">
{dashboard.serviceGroups.map((group) => (
<ServicePanel key={group.id} group={group} />
))}
</section>
<StatusStrip
id={dashboard.statusStripId}
items={dashboard.statusItems}
/>
</main>
);
}

View file

@ -1,30 +0,0 @@
import { useId } from "react";
import type { UiModuleBlock } from "../types";
import { ModuleCard } from "./ModuleCard";
export interface DashboardHeaderProps {
title: string;
subtitle?: string;
eyebrow?: string;
module?: UiModuleBlock;
}
export function DashboardHeader({
title,
subtitle,
eyebrow,
module,
}: DashboardHeaderProps) {
const titleId = useId();
return (
<header className="dashboard-header" aria-labelledby={titleId}>
<div>
{eyebrow ? <p>{eyebrow}</p> : null}
<h1 id={titleId}>{title}</h1>
{subtitle ? <span>{subtitle}</span> : null}
</div>
{module ? <ModuleCard module={module} /> : null}
</header>
);
}

View file

@ -1,24 +0,0 @@
export interface DiagonalStripeFieldProps {
density?: "open" | "regular" | "tight";
direction?: "forward" | "backward";
size?: "sm" | "md" | "lg";
tone?: "neutral" | "accent" | "warning" | "danger";
}
export function DiagonalStripeField({
density = "regular",
direction = "forward",
size = "md",
tone = "accent",
}: DiagonalStripeFieldProps) {
return (
<div
className="diagonal-stripe-field"
data-density={density}
data-direction={direction}
data-size={size}
data-tone={tone}
aria-hidden="true"
/>
);
}

View file

@ -1,42 +0,0 @@
import type { UiStatusItem } from "../types";
export interface FooterCellProps {
item: UiStatusItem;
}
export function FooterCell({ item }: FooterCellProps) {
const target = item.link?.external ? "_blank" : undefined;
const rel = item.link?.external ? "noreferrer" : undefined;
const content = (
<>
<span>{item.label}</span>
<strong>{item.value}</strong>
</>
);
if (item.link) {
return (
<a
className="footer-cell"
data-severity={item.severity || "neutral"}
data-model-id={item.id}
href={item.link.href}
target={target}
rel={rel}
aria-label={item.link.label}
>
{content}
</a>
);
}
return (
<div
className="footer-cell"
data-severity={item.severity || "neutral"}
data-model-id={item.id}
>
{content}
</div>
);
}

View file

@ -1,6 +0,0 @@
import type { UiStatusItem } from "../types";
import { FooterCell } from "./FooterCell";
export function FooterStatusCell({ item }: { item: UiStatusItem }) {
return <FooterCell item={item} />;
}

View file

@ -1,13 +0,0 @@
import type { PropsWithChildren } from "react";
export interface GridFrameProps extends PropsWithChildren {
density?: "compact" | "dense";
}
export function GridFrame({ children, density = "dense" }: GridFrameProps) {
return (
<section className="grid-frame" data-density={density}>
{children}
</section>
);
}

View file

@ -1,33 +0,0 @@
import type { ButtonHTMLAttributes } from "react";
import { IconGlyph } from "./IconGlyph";
export interface IconButtonProps
extends Omit<ButtonHTMLAttributes<HTMLButtonElement>, "type"> {
icon: string;
label: string;
active?: boolean;
type?: "button" | "submit" | "reset";
}
export function IconButton({
icon,
label,
active = false,
disabled = false,
type = "button",
className = "",
...buttonProps
}: IconButtonProps) {
return (
<button
{...buttonProps}
className={className ? `icon-button ${className} ` : "icon-button "}
aria-label={label}
data-active={active}
disabled={disabled}
type={type}
>
<IconGlyph name={icon} size="sm" />
</button>
);
}

View file

@ -1,21 +0,0 @@
import { Icon } from "@iconify/react";
export interface IconGlyphProps {
name?: string;
label?: string;
size?: "sm" | "md" | "lg";
}
export function IconGlyph({ name, label, size = "md" }: IconGlyphProps) {
return (
<span
className="icon-glyph"
data-size={size}
data-icon-name={name}
aria-label={label}
aria-hidden={label ? undefined : "true"}
>
{name ? <Icon icon={name} /> : null}
</span>
);
}

View file

@ -1,114 +0,0 @@
import "uplot/dist/uPlot.min.css";
import { useEffect, useRef } from "react";
import type { UiSeverity } from "../types";
export interface LineChartProps {
values?: number[];
severity?: UiSeverity;
label?: string;
}
export function LineChart({
values = [],
severity = "neutral",
label = "Telemetry trend",
}: LineChartProps) {
const chartElementRef = useRef<HTMLDivElement>(null);
const valuesKey = values.join(",");
useEffect(() => {
const chartElement = chartElementRef.current;
if (!chartElement) return;
let chart: ChartInstance | null = null;
let resizeObserver: ResizeObserver | null = null;
let disposed = false;
void import("uplot").then((module) => {
if (disposed || !chartElementRef.current) return;
const element = chartElementRef.current;
chart = new module.default(chartOptions(element, severity), chartData(values), element);
if (typeof ResizeObserver !== "undefined") {
resizeObserver = new ResizeObserver(() => {
chart?.setSize(chartSize(element));
});
resizeObserver.observe(element);
}
});
return () => {
disposed = true;
resizeObserver?.disconnect();
chart?.destroy();
};
}, [severity, valuesKey, values]);
return (
<div
className="line-chart"
data-chart-library="uplot"
data-severity={severity}
data-values={values.join(",")}
role="img"
aria-label={label}
>
<div ref={chartElementRef} className="line-chart__canvas" aria-hidden="true" />
</div>
);
}
type ChartInstance = {
destroy(): void;
setSize(size: { width: number; height: number }): void;
};
function chartData(values: number[]): import("uplot").AlignedData {
const normalized = values.length ? values : [0, 0];
return [
normalized.map((_, index) => index),
normalized.map((value) => Math.max(0, Number(value) || 0)),
];
}
function chartOptions(
element: HTMLElement,
severity: UiSeverity,
): import("uplot").Options {
return {
...chartSize(element),
cursor: { show: false },
legend: { show: false },
padding: [2, 0, 2, 0],
scales: {
x: { time: false },
y: { auto: true },
},
axes: [{ show: false }, { show: false }],
series: [
{},
{
stroke: chartStroke(element, severity),
width: 2,
points: { show: false },
},
],
};
}
function chartSize(element?: HTMLElement): { width: number; height: number } {
return {
width: Math.max(80, Math.round(element?.clientWidth || 120)),
height: Math.max(20, Math.round(element?.clientHeight || 24)),
};
}
function chartStroke(element: HTMLElement, severity: UiSeverity): string {
const styles = window.getComputedStyle(element.closest(".line-chart") || element);
const currentColor = styles.color;
if (currentColor) return currentColor;
if (severity === "danger") return "#ff1744";
if (severity === "warning") return "#ffb020";
return "#d7ff00";
}

View file

@ -1,32 +0,0 @@
import { useId } from "react";
import type { UiModuleBlock } from "../types";
import { IconGlyph } from "./IconGlyph";
export interface ModuleCardProps {
module: UiModuleBlock;
}
export function ModuleCard({ module }: ModuleCardProps) {
const generatedId = useId();
const titleId = `${generatedId}-title`;
const ariaLabel = module.title ? undefined : module.label || module.id;
return (
<aside
className="module-card"
data-severity={module.severity || "neutral"}
data-model-id={module.id}
aria-labelledby={module.title ? titleId : undefined}
aria-label={ariaLabel}
>
<div>
{module.title ? <h2 id={titleId}>{module.title}</h2> : null}
{module.value ? <strong>{module.value}</strong> : null}
{module.detail || module.label ? (
<p>{module.detail || module.label}</p>
) : null}
</div>
{module.icon ? <IconGlyph name={module.icon} size="lg" /> : null}
</aside>
);
}

View file

@ -1,19 +0,0 @@
import type { PropsWithChildren } from "react";
export interface PanelProps extends PropsWithChildren {
title?: string;
density?: "compact" | "dense";
}
export function Panel({ title, density = "dense", children }: PanelProps) {
return (
<section className="panel" data-density={density}>
{title ? (
<header className="panel__header">
<h2>{title}</h2>
</header>
) : null}
<div className="panel__body">{children}</div>
</section>
);
}

View file

@ -1,34 +0,0 @@
import type { CSSProperties } from "react";
import { clampPercent } from "../format";
import type { UiSeverity } from "../types";
export interface ProgressMeterProps {
value?: number;
severity?: UiSeverity;
label?: string;
}
export function ProgressMeter({
value,
severity = "neutral",
label = "Progress",
}: ProgressMeterProps) {
const progress = value === undefined ? null : clampPercent(value);
return (
<div
className="progress-meter"
data-severity={severity}
role="meter"
aria-label={label}
aria-valuemin={0}
aria-valuemax={100}
aria-valuenow={progress ?? undefined}
data-empty={progress === null}
>
{progress !== null ? (
<span style={{ "--meter-progress": `${progress}%` } as CSSProperties} />
) : null}
</div>
);
}

View file

@ -1,21 +0,0 @@
export interface ScanlineFieldProps {
intensity?: "soft" | "medium" | "hard";
size?: "sm" | "md" | "lg";
tone?: "neutral" | "accent" | "warning";
}
export function ScanlineField({
intensity = "medium",
size = "md",
tone = "neutral",
}: ScanlineFieldProps) {
return (
<div
className="scanline-field"
data-intensity={intensity}
data-size={size}
data-tone={tone}
aria-hidden="true"
/>
);
}

View file

@ -1,19 +0,0 @@
export interface SeparatorProps {
orientation?: "horizontal" | "vertical";
dense?: boolean;
}
export function Separator({
orientation = "horizontal",
dense = false,
}: SeparatorProps) {
return (
<div
className="separator"
data-orientation={orientation}
data-dense={dense}
role="separator"
aria-orientation={orientation}
/>
);
}

View file

@ -1,6 +0,0 @@
import type { UiServiceGroup } from "../types";
import { ServicePanel } from "./ServicePanel";
export function ServiceGroupPanel({ group }: { group: UiServiceGroup }) {
return <ServicePanel group={group} />;
}

View file

@ -1,27 +0,0 @@
import type { UiServiceGroup } from "../types";
import { Panel } from "./Panel";
import { ServiceRow } from "./ServiceRow";
import { StatusStrip } from "./StatusStrip";
export interface ServicePanelProps {
group: UiServiceGroup;
}
export function ServicePanel({ group }: ServicePanelProps) {
return (
<div
className="service-panel"
data-layout={group.layout || "list"}
data-model-id={group.id}
>
<Panel title={group.title}>
{group.summary?.length ? (
<StatusStrip id={`${group.id}:summary`} items={group.summary} compact />
) : null}
{group.services.map((service) => (
<ServiceRow key={service.id} service={service} />
))}
</Panel>
</div>
);
}

View file

@ -1,56 +0,0 @@
import type { UiServiceRow } from "../types";
import { IconGlyph } from "./IconGlyph";
import { StatusBadge } from "./StatusBadge";
export interface ServiceRowProps {
service: UiServiceRow;
}
export function ServiceRow({ service }: ServiceRowProps) {
const target = service.link?.external ? "_blank" : undefined;
const rel = service.link?.external ? "noreferrer" : undefined;
const content = (
<>
<span className="service-row__status" aria-hidden="true" />
<IconGlyph name={service.icon} />
<div className="service-row__main">
<h3>{service.label}</h3>
<p>{service.description}</p>
</div>
{service.detail ? (
<StatusBadge label={service.detail} severity={service.severity} />
) : null}
{service.link ? (
<span className="service-row__launch" aria-hidden="true">
<IconGlyph name="mdi:open-in-new" size="sm" />
</span>
) : null}
</>
);
if (service.link) {
return (
<a
className="service-row"
data-severity={service.severity}
data-model-id={service.id}
href={service.link.href}
target={target}
rel={rel}
aria-label={service.link.label}
>
{content}
</a>
);
}
return (
<article
className="service-row"
data-severity={service.severity}
data-model-id={service.id}
>
{content}
</article>
);
}

View file

@ -1,26 +0,0 @@
export interface SignalTraceProps {
density?: "regular" | "tight";
orientation?: "horizontal" | "vertical";
tone?: "neutral" | "accent" | "warning";
}
export function SignalTrace({
density = "regular",
orientation = "horizontal",
tone = "accent",
}: SignalTraceProps) {
return (
<div
className="signal-trace"
data-density={density}
data-orientation={orientation}
data-tone={tone}
aria-hidden="true"
>
<span className="signal-trace__rail" />
<span className="signal-trace__node" data-node="start" />
<span className="signal-trace__node" data-node="middle" />
<span className="signal-trace__node" data-node="end" />
</div>
);
}

View file

@ -1,37 +0,0 @@
import type { UiSeverity } from "../types";
export interface SparklineProps {
values?: number[];
severity?: UiSeverity;
}
export function Sparkline({
values = [],
severity = "neutral",
}: SparklineProps) {
const points = toPoints(values);
return (
<svg
className="sparkline"
data-severity={severity}
viewBox="0 0 100 24"
aria-hidden="true"
>
{points ? <polyline points={points} /> : null}
</svg>
);
}
function toPoints(values: number[]): string {
if (values.length === 0) return "";
const max = Math.max(...values, 1);
const step = values.length > 1 ? 100 / (values.length - 1) : 100;
return values
.map((value, index) => {
const x = index * step;
const y = 24 - (Math.max(0, value) / max) * 22;
return `${x.toFixed(2)},${y.toFixed(2)}`;
})
.join(" ");
}

View file

@ -1,14 +0,0 @@
import type { UiSeverity } from "../types";
export interface StatusBadgeProps {
label: string;
severity?: UiSeverity;
}
export function StatusBadge({ label, severity = "neutral" }: StatusBadgeProps) {
return (
<span className="status-badge" data-severity={severity}>
{label}
</span>
);
}

View file

@ -1,23 +0,0 @@
import type { UiStatusItem } from "../types";
import { FooterCell } from "./FooterCell";
export interface StatusStripProps {
id?: string;
items: UiStatusItem[];
compact?: boolean;
}
export function StatusStrip({ id, items, compact = false }: StatusStripProps) {
return (
<section
className="status-strip"
data-compact={compact}
data-model-id={id}
data-variant={compact ? "cluster" : "system-bus"}
>
{items.map((item) => (
<FooterCell key={item.id} item={item} />
))}
</section>
);
}

View file

@ -1,26 +0,0 @@
import type { UiSeverity } from "../types";
import { IconGlyph } from "./IconGlyph";
export interface SystemStateProps {
title: string;
detail?: string;
icon?: string;
severity?: UiSeverity;
}
export function SystemState({
title,
detail,
icon = "mdi:information-outline",
severity = "neutral",
}: SystemStateProps) {
return (
<section className="system-state" data-severity={severity}>
<IconGlyph name={icon} size="lg" />
<div>
<h2>{title}</h2>
{detail ? <p>{detail}</p> : null}
</div>
</section>
);
}

View file

@ -1,59 +0,0 @@
import type { CSSProperties } from "react";
import { clampPercent, formatMetricValue } from "../format";
import type { UiTelemetryCard } from "../types";
import { IconGlyph } from "./IconGlyph";
import { LineChart } from "./LineChart";
export interface TelemetryCardProps {
card: UiTelemetryCard;
index?: number;
}
export function TelemetryCard({ card, index }: TelemetryCardProps) {
const progress = resolveProgress(card);
const value = formatMetricValue(card.value);
const metricIndex = index ? String(index).padStart(2, "0") : undefined;
return (
<article
className="telemetry-card"
data-severity={card.severity}
data-model-id={card.id}
data-metric-index={metricIndex}
>
<header>
<div>
{metricIndex ? (
<span className="telemetry-card__index">{metricIndex}</span>
) : null}
<h3>{card.label}</h3>
</div>
{card.icon ? <IconGlyph name={card.icon} size="sm" /> : null}
</header>
<strong>{value}</strong>
{progress !== null ? (
<div className="telemetry-card__bar" aria-hidden="true">
<span style={{ "--metric-progress": `${progress}%` } as CSSProperties} />
</div>
) : null}
{card.sparkline?.length ? (
<LineChart
values={card.sparkline}
severity={card.severity}
label={`${card.label} trend`}
/>
) : null}
{card.detail || card.description ? (
<p className="telemetry-card__source">{card.detail || card.description}</p>
) : null}
</article>
);
}
function resolveProgress(card: UiTelemetryCard): number | null {
if (typeof card.progress === "number") return clampPercent(card.progress);
if (card.value.kind !== "percent") return null;
const progress = Number(card.value.value);
return Number.isFinite(progress) ? clampPercent(progress) : null;
}

View file

@ -1,29 +0,0 @@
import { useId } from "react";
import type { UiTelemetryCard } from "../types";
import { TelemetryCard } from "./TelemetryCard";
export interface TelemetryGridProps {
cards: UiTelemetryCard[];
}
export function TelemetryGrid({ cards }: TelemetryGridProps) {
const headingId = useId();
const metricCount = `${cards.length} ${cards.length === 1 ? "Metric" : "Metrics"}`;
return (
<section className="telemetry-section" aria-labelledby={headingId}>
<header className="telemetry-section__header">
<div>
<p>Signal Matrix</p>
<h2 id={headingId}>Telemetry</h2>
</div>
<span>{metricCount}</span>
</header>
<div className="telemetry-grid">
{cards.map((card, index) => (
<TelemetryCard key={card.id} card={card} index={index + 1} />
))}
</div>
</section>
);
}

View file

@ -1,6 +0,0 @@
import type { UiTelemetryCard } from "../types";
import { TelemetryGrid } from "./TelemetryGrid";
export function TelemetryStrip({ cards }: { cards: UiTelemetryCard[] }) {
return <TelemetryGrid cards={cards} />;
}

View file

@ -1,36 +0,0 @@
import type { UiTheme } from "../theme";
import { getNextUiTheme } from "../theme";
import { IconGlyph } from "./IconGlyph";
export interface ThemeToggleProps {
theme: UiTheme;
onThemeChange: (theme: UiTheme) => void;
}
export function ThemeToggle({ theme, onThemeChange }: ThemeToggleProps) {
const nextTheme = getNextUiTheme(theme);
return (
<button
aria-label="Light theme"
aria-pressed={theme === "light"}
className="theme-toggle"
data-ui-theme-current={theme}
data-ui-theme-toggle="true"
onClick={() => onThemeChange(nextTheme)}
type="button"
>
<span className="theme-toggle__label">Theme</span>
<span className="theme-toggle__switch" aria-hidden="true">
<span className="theme-toggle__cell" data-active={theme === "dark"}>
<IconGlyph name="mdi:weather-night" size="sm" />
<span>Dark</span>
</span>
<span className="theme-toggle__cell" data-active={theme === "light"}>
<IconGlyph name="mdi:white-balance-sunny" size="sm" />
<span>Light</span>
</span>
</span>
</button>
);
}

View file

@ -1,6 +0,0 @@
import type { UiModuleBlock } from "../types";
import { ModuleCard } from "./ModuleCard";
export function WeatherModule({ module }: { module: UiModuleBlock }) {
return <ModuleCard module={module} />;
}

View file

@ -1,202 +0,0 @@
import { renderToString } from "react-dom/server";
import { describe, expect, test } from "vitest";
import { Button } from "./Button";
import { DashboardFrame } from "./DashboardFrame";
import { FooterCell } from "./FooterCell";
import { IconButton } from "./IconButton";
import { ServiceRow } from "./ServiceRow";
import { StatusStrip } from "./StatusStrip";
import { TelemetryCard } from "./TelemetryCard";
import { TelemetryGrid } from "./TelemetryGrid";
import { ThemeToggle } from "./ThemeToggle";
import { dashboardPreviewFixtures } from "../fixtures";
describe("dashboard UI components", () => {
test("renders the primary generic dashboard fixture", () => {
const body = renderToString(
<DashboardFrame dashboard={dashboardPreviewFixtures.primary} />,
);
expect(body).toContain("Operations Console");
expect(body).toContain("Core Throughput");
expect(body).toContain("Queue Workers");
expect(body).toContain("console-header");
expect(body).toContain("telemetry-section");
expect(body).toContain("6 Metrics");
expect(body).toContain('data-variant="system-bus"');
expect(body).toContain("data-icon-name=\"mdi:server-network\"");
expect(body).toContain("data-severity=\"warning\"");
expect(body).not.toContain("dashboard-title");
});
test("renders another generic dashboard fixture through the same component", () => {
const body = renderToString(
<DashboardFrame dashboard={dashboardPreviewFixtures.secondary} />,
);
expect(body).toContain("Support Desk");
expect(body).toContain("Response Window");
expect(body).toContain("Regional Nodes");
expect(body).toContain("data-icon-name=\"mdi:headset\"");
expect(body).toContain("data-severity=\"loading\"");
});
test("renders optional service and status links as focusable anchors", () => {
const service = renderToString(
<ServiceRow
service={{
id: "linked-service",
label: "Linked Service",
description: "Generic linked destination",
severity: "ok",
detail: "ready",
link: { href: "https://example.test/service", external: true },
}}
/>,
);
const footer = renderToString(
<FooterCell
item={{
id: "linked-status",
label: "Linked Status",
value: "open",
severity: "neutral",
link: { href: "https://example.test/status" },
}}
/>,
);
expect(service).toContain("<a ");
expect(service).toContain("href=\"https://example.test/service\"");
expect(service).toContain("target=\"_blank\"");
expect(service).toContain('class="service-row__status"');
expect(service).toContain('class="service-row__launch"');
expect(footer).toContain("<a ");
expect(footer).toContain("href=\"https://example.test/status\"");
});
test("renders the status strip as a compact system bus", () => {
const body = renderToString(
<StatusStrip
id="dashboard-status"
items={[
{ id: "system", label: "System", value: "Nominal", severity: "ok" },
{ id: "refresh", label: "Refresh", value: "15s", severity: "neutral" },
]}
/>,
);
expect(body).toContain('class="status-strip"');
expect(body).toContain('data-variant="system-bus"');
expect(body).toContain("Nominal");
});
test("renders stable model IDs on group and status containers", () => {
const body = renderToString(
<DashboardFrame dashboard={dashboardPreviewFixtures.primary} />,
);
expect(body).toContain("data-model-id=\"queue-workers\"");
expect(body).toContain("data-model-id=\"dashboard-status\"");
});
test("does not render progress bars for non-percent metrics without explicit progress", () => {
const withoutProgress = renderToString(
<TelemetryCard
card={{
id: "bytes",
label: "Bytes Metric",
value: { kind: "bytes", value: 2048 },
severity: "neutral",
}}
/>,
);
const withProgress = renderToString(
<TelemetryCard
card={{
id: "bytes-with-progress",
label: "Bytes With Progress",
value: { kind: "bytes", value: 2048 },
progress: 42,
severity: "neutral",
}}
/>,
);
expect(withoutProgress).not.toContain("telemetry-card__bar");
expect(withProgress).toContain("--metric-progress:42%");
});
test("renders telemetry trends through the uPlot chart surface", () => {
const body = renderToString(
<TelemetryCard
card={{
id: "trend-card",
label: "Trend Card",
value: { kind: "percent", value: 64 },
sparkline: [18, 24, 64],
severity: "ok",
}}
/>,
);
expect(body).toContain('data-chart-library="uplot"');
});
test("renders telemetry as a framed instrument section with indexed cards", () => {
const body = renderToString(
<TelemetryGrid cards={dashboardPreviewFixtures.primary.telemetry.slice(0, 2)} />,
);
expect(body).toContain('class="telemetry-section"');
expect(body).toContain("Telemetry");
expect(body).toContain("2 Metrics");
expect(body).toContain('data-metric-index="01"');
expect(body).toContain('class="telemetry-card__index"');
expect(body).toContain('class="telemetry-card__source"');
});
test("base button controls forward native attributes", () => {
const button = renderToString(
<Button
label="Refresh"
id="refresh-action"
className="custom-action"
aria-controls="refresh-target"
/>,
);
const iconButton = renderToString(
<IconButton
icon="mdi:refresh"
label="Refresh status"
id="refresh-icon-action"
className="custom-icon-action"
aria-expanded="false"
/>,
);
expect(button).toContain("id=\"refresh-action\"");
expect(button).toContain("class=\"ui-button custom-action ");
expect(button).toContain("aria-controls=\"refresh-target\"");
expect(iconButton).toContain("id=\"refresh-icon-action\"");
expect(iconButton).toContain("class=\"icon-button custom-icon-action ");
expect(iconButton).toContain("aria-expanded=\"false\"");
});
test("renders the theme toggle as a segmented instrument control", () => {
const body = renderToString(
<ThemeToggle theme="light" onThemeChange={() => undefined} />,
);
expect(body).toContain('class="theme-toggle"');
expect(body).toContain('data-ui-theme-current="light"');
expect(body).toContain('aria-label="Light theme"');
expect(body).toContain('aria-pressed="true"');
expect(body).toContain('class="theme-toggle__label"');
expect(body).toContain('class="theme-toggle__switch"');
expect(body).toContain('data-active="true"');
expect(body).toContain("Theme");
expect(body).toContain("Dark");
expect(body).toContain("Light");
});
});

View file

@ -1,957 +0,0 @@
.dashboard-frame {
display: grid;
box-sizing: border-box;
height: 100vh;
min-height: 100vh;
gap: 0.45rem;
grid-template-rows: auto auto minmax(0, 1fr) auto;
overflow: hidden;
padding: clamp(0.5rem, 0.72vw, 0.74rem);
background:
radial-gradient(ellipse at 50% 12%, transparent 0 44%, rgba(0, 0, 0, 0.14) 100%),
linear-gradient(180deg, rgba(255, 255, 255, 0.025), transparent 9rem),
var(--ui-color-frame-wash);
font-variant-numeric: tabular-nums;
}
.dashboard-frame__header,
.dashboard-header {
display: grid;
gap: 0.48rem;
align-items: start;
}
.dashboard-frame__header {
grid-template-columns: minmax(28rem, 1fr) minmax(31rem, 0.92fr);
min-height: 4.82rem;
border: var(--ui-border-strong);
background: var(--ui-color-surface-panel);
box-shadow: var(--ui-shadow-inset-panel);
padding: 0.48rem;
}
.dashboard-frame__title {
display: grid;
grid-template-columns: minmax(0, 1fr) auto;
gap: 0.58rem;
align-items: stretch;
min-width: 0;
min-height: 3.72rem;
border-right: var(--ui-border);
padding-right: 0.48rem;
}
.dashboard-frame__title-copy {
display: grid;
align-content: start;
min-width: 0;
}
.dashboard-frame__actions {
justify-self: end;
}
.dashboard-header {
grid-template-columns: minmax(0, 1fr) auto;
padding: var(--ui-space-3);
}
.dashboard-frame__header p,
.dashboard-frame__header span,
.dashboard-header p,
.dashboard-header span,
.dashboard-frame h1,
.dashboard-header h1 {
margin: 0;
text-transform: uppercase;
}
.dashboard-frame__header p,
.dashboard-frame__header span,
.dashboard-header p,
.dashboard-header span {
color: var(--ui-color-muted);
font-size: 0.62rem;
font-weight: 800;
letter-spacing: 0.04em;
}
.dashboard-frame h1,
.dashboard-header h1 {
max-width: 100%;
overflow-wrap: anywhere;
font-family: var(--ui-font-display);
font-size: clamp(2.05rem, 3vw, 2.78rem);
font-weight: 850;
letter-spacing: 0;
line-height: 0.82;
white-space: nowrap;
}
.dashboard-frame__modules {
display: grid;
grid-template-columns: minmax(15rem, 0.66fr) minmax(17rem, 1fr);
justify-content: end;
gap: 0.48rem;
}
.dashboard-frame__panels {
display: grid;
grid-template-columns: repeat(4, minmax(0, 1fr));
align-content: start;
gap: 0.45rem;
min-height: 0;
overflow: hidden;
}
.dashboard-frame__panels .service-panel[data-layout="grid"] {
grid-column: 1 / -1;
}
.dashboard-frame__panels .service-panel[data-layout="grid"] .panel__body {
grid-template-columns: repeat(4, minmax(0, 1fr));
}
.telemetry-section {
display: grid;
min-height: 0;
border: var(--ui-border-strong);
background: var(--ui-color-surface-panel);
box-shadow: var(--ui-shadow-inset-panel);
}
.telemetry-section__header {
display: grid;
grid-template-columns: minmax(0, 1fr) auto;
align-items: center;
min-height: 1.48rem;
border-bottom: var(--ui-border);
padding: 0.22rem 0.5rem;
}
.telemetry-section__header p,
.telemetry-section__header h2,
.telemetry-section__header span {
margin: 0;
text-transform: uppercase;
}
.telemetry-section__header p {
color: var(--ui-color-muted);
font-size: 0.46rem;
font-weight: 800;
letter-spacing: 0.08em;
}
.telemetry-section__header h2 {
color: var(--ui-color-text);
font-size: 0.68rem;
font-weight: 850;
letter-spacing: 0.04em;
}
.telemetry-section__header > span {
border: var(--ui-border);
color: var(--ui-color-text-secondary);
font-size: 0.5rem;
font-weight: 850;
letter-spacing: 0.06em;
padding: 0.18rem 0.36rem;
}
.telemetry-grid {
display: grid;
grid-template-columns: repeat(8, minmax(0, 1fr));
gap: 1px;
border: 0;
background: var(--ui-color-border-subtle);
}
.telemetry-card,
.module-card,
.panel,
.system-state {
border: var(--ui-border);
background: var(--ui-color-surface-panel);
color: var(--ui-color-text);
}
.telemetry-card {
position: relative;
display: grid;
min-height: 4.72rem;
gap: 0.22rem;
background: linear-gradient(
180deg,
var(--ui-color-card-gradient-start),
var(--ui-color-card-gradient-end)
);
border-left: 2px solid var(--ui-color-ok);
box-shadow: var(--ui-shadow-inset-panel);
padding: 0.34rem 0.46rem 0.36rem;
}
.telemetry-card[data-severity="warning"] {
border-color: color-mix(in srgb, var(--ui-color-warning), transparent 42%);
border-left-color: var(--ui-color-warning);
}
.telemetry-card[data-severity="danger"] {
border-color: color-mix(in srgb, var(--ui-color-danger), transparent 22%);
border-left-color: var(--ui-color-danger);
color: var(--ui-color-danger);
}
.telemetry-card[data-severity="stale"],
.telemetry-card[data-severity="unavailable"] {
border-left-color: var(--ui-color-stale);
color: var(--ui-color-stale);
}
.telemetry-card[data-severity="loading"] {
border-left-color: var(--ui-color-accent);
color: var(--ui-color-accent);
}
.telemetry-card:hover {
background: var(--ui-color-surface-raised);
}
.telemetry-card header {
display: grid;
grid-template-columns: minmax(0, 1fr) auto;
min-width: 0;
align-items: start;
gap: 0.25rem;
}
.telemetry-card header .icon-glyph {
width: 1rem;
height: 1rem;
border-color: var(--ui-color-border-subtle);
background: transparent;
color: var(--ui-color-muted);
}
.telemetry-card h3,
.telemetry-card p,
.module-card h2,
.module-card p,
.service-row h3,
.service-row p,
.panel h2,
.system-state h2,
.system-state p {
margin: 0;
}
.telemetry-card h3,
.service-row h3 {
overflow: hidden;
font-weight: 850;
letter-spacing: 0;
line-height: 1.05;
text-overflow: ellipsis;
text-transform: uppercase;
white-space: nowrap;
}
.telemetry-card h3 {
overflow-wrap: anywhere;
color: var(--ui-color-text-secondary);
font-size: 0.52rem;
font-weight: 850;
line-height: 1.02;
white-space: normal;
}
.telemetry-card__index {
display: inline-grid;
min-width: 1.05rem;
margin-bottom: 0.08rem;
color: var(--ui-color-muted);
font-size: 0.45rem;
font-weight: 850;
line-height: 1;
}
.service-row h3 {
font-size: 0.72rem;
}
.telemetry-card strong,
.module-card strong {
display: block;
font-family: var(--ui-font-display);
font-size: clamp(1.45rem, 2.25vw, 2.05rem);
font-weight: 800;
letter-spacing: 0;
line-height: 0.78;
white-space: nowrap;
}
.telemetry-card p,
.module-card p,
.service-row p {
overflow: hidden;
color: var(--ui-color-muted);
line-height: 1.08;
text-overflow: ellipsis;
text-transform: uppercase;
white-space: nowrap;
}
.telemetry-card p,
.telemetry-card__source {
font-size: 0.49rem;
line-height: 1.05;
}
.module-card p,
.service-row p {
font-size: 0.56rem;
}
.telemetry-card__bar,
.progress-meter {
height: 0.2rem;
border: var(--ui-border);
background: var(--ui-color-surface-inset);
}
.telemetry-card__bar span,
.progress-meter span {
display: block;
width: var(--metric-progress, var(--meter-progress));
height: 100%;
background: currentColor;
}
.telemetry-card[data-severity="loading"] .telemetry-card__bar span {
min-width: 1.25rem;
}
.line-chart {
position: relative;
min-width: 0;
height: 0.86rem;
color: var(--ui-color-accent);
}
.line-chart::before {
position: absolute;
inset: 50% 0 auto;
height: 1px;
background: var(--ui-color-border-subtle);
content: "";
}
.line-chart[data-severity="warning"] {
color: var(--ui-color-warning);
}
.line-chart[data-severity="danger"] {
color: var(--ui-color-danger);
}
.line-chart[data-severity="stale"],
.line-chart[data-severity="unavailable"] {
color: var(--ui-color-stale);
}
.line-chart__canvas {
width: 100%;
height: 100%;
}
.line-chart__canvas .uplot {
width: 100% !important;
height: 100% !important;
background: transparent;
font-family: var(--ui-font-mono);
}
.line-chart__canvas .u-over,
.line-chart__canvas .u-under {
overflow: visible;
}
.module-card {
display: grid;
grid-template-columns: minmax(0, 1fr) auto;
gap: 0.45rem;
align-items: start;
min-width: 0;
min-height: 3.72rem;
border: var(--ui-border);
background: var(--ui-color-surface-module);
box-shadow: var(--ui-shadow-inset-panel);
padding: 0.42rem 0.48rem;
}
.module-card h2 {
color: var(--ui-color-muted);
font-size: 0.48rem;
font-weight: 850;
letter-spacing: 0.08em;
line-height: 1;
text-transform: uppercase;
}
.module-card strong {
margin-top: 0.12rem;
font-size: clamp(1.1rem, 1.55vw, 1.56rem);
line-height: 0.82;
overflow-wrap: anywhere;
}
.module-card p {
margin-top: 0.18rem;
color: var(--ui-color-muted);
font-size: 0.45rem;
line-height: 1.05;
}
.module-card[data-severity="ok"] .icon-glyph {
border-color: color-mix(in srgb, var(--ui-color-ok), transparent 44%);
background: var(--ui-color-accent-soft);
color: var(--ui-color-ok);
}
.module-card[data-severity="warning"] {
border-color: color-mix(in srgb, var(--ui-color-warning), transparent 42%);
color: var(--ui-color-warning);
}
.module-card[data-severity="danger"] {
border-color: color-mix(in srgb, var(--ui-color-danger), transparent 32%);
color: var(--ui-color-danger);
}
.module-card[data-severity="stale"],
.module-card[data-severity="unavailable"] {
color: var(--ui-color-stale);
}
.module-card[data-severity="loading"] {
color: var(--ui-color-accent);
}
.service-row {
display: grid;
grid-template-columns: 0.28rem auto minmax(0, 1fr) auto auto;
gap: 0.24rem;
align-items: center;
min-height: 2.08rem;
border: 0;
border-bottom: var(--ui-border);
background: transparent;
color: inherit;
padding: 0.14rem 0;
text-decoration: none;
}
.service-row__status {
width: 0.26rem;
height: 0.72rem;
background: var(--ui-color-muted);
}
.service-row[data-severity="ok"] .service-row__status {
background: var(--ui-color-ok);
}
.service-row[data-severity="warning"] {
border-bottom-color: color-mix(in srgb, var(--ui-color-warning), transparent 54%);
}
.service-row[data-severity="warning"] .service-row__status {
background: var(--ui-color-warning);
}
.service-row[data-severity="danger"],
.service-row[data-severity="unavailable"] {
border-bottom-color: color-mix(in srgb, var(--ui-color-danger), transparent 50%);
}
.service-row[data-severity="danger"] .service-row__status,
.service-row[data-severity="unavailable"] .service-row__status {
background: var(--ui-color-danger);
}
.service-row[data-severity="loading"] {
border-bottom-color: color-mix(in srgb, var(--ui-color-accent), transparent 56%);
}
.service-row[data-severity="loading"] .service-row__status {
background: var(--ui-color-accent);
}
.service-row:where(a):hover {
border-bottom-color: var(--ui-color-accent);
background: var(--ui-color-hover);
}
.service-row__main {
min-width: 0;
}
.service-row__launch {
display: inline-grid;
place-items: center;
color: var(--ui-color-muted);
}
.service-row__launch .icon-glyph {
width: 1rem;
height: 1rem;
border-color: var(--ui-color-border-subtle);
background: transparent;
}
.status-badge {
display: inline-grid;
min-height: 1.12rem;
align-items: center;
border: var(--ui-border);
background: var(--ui-color-surface-inset);
padding: 0 0.32rem;
color: var(--ui-color-muted);
font-size: 0.5rem;
font-weight: 800;
letter-spacing: 0.04em;
text-transform: uppercase;
white-space: nowrap;
}
.status-badge[data-severity="ok"] {
border-color: color-mix(in srgb, var(--ui-color-ok), transparent 42%);
color: var(--ui-color-ok);
}
.status-badge[data-severity="warning"] {
border-color: color-mix(in srgb, var(--ui-color-warning), transparent 35%);
color: var(--ui-color-warning);
}
.status-badge[data-severity="danger"] {
border-color: color-mix(in srgb, var(--ui-color-danger), transparent 30%);
color: var(--ui-color-danger);
}
.status-badge[data-severity="stale"],
.status-badge[data-severity="unavailable"] {
color: var(--ui-color-stale);
}
.status-badge[data-severity="loading"] {
color: var(--ui-color-accent);
}
.footer-cell {
display: grid;
grid-template-columns: auto minmax(0, 1fr);
min-height: 1.72rem;
align-items: center;
background: var(--ui-color-surface-footer);
color: inherit;
text-decoration: none;
}
.footer-cell span,
.footer-cell strong {
min-width: 0;
padding: 0.29rem 0.52rem;
overflow-wrap: anywhere;
text-transform: uppercase;
}
.footer-cell span {
height: 100%;
border-right: var(--ui-border);
color: var(--ui-color-muted);
font-size: 0.48rem;
font-weight: 800;
letter-spacing: 0.04em;
}
.footer-cell strong {
color: var(--ui-color-text);
font-size: 0.54rem;
font-weight: 850;
}
.footer-cell[data-severity="ok"] strong {
color: var(--ui-color-ok);
}
.footer-cell[data-severity="warning"] strong {
color: var(--ui-color-warning);
}
.footer-cell[data-severity="danger"] strong {
color: var(--ui-color-danger);
}
.footer-cell[data-severity="stale"] strong,
.footer-cell[data-severity="unavailable"] strong {
color: var(--ui-color-stale);
}
.footer-cell[data-severity="loading"] strong {
color: var(--ui-color-accent);
}
.footer-cell:where(a):hover strong {
color: var(--ui-color-accent);
}
.status-strip {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(9rem, 1fr));
gap: 1px;
border: var(--ui-border-strong);
background: var(--ui-color-border);
box-shadow: var(--ui-shadow-inset-panel);
}
.status-strip[data-compact="true"] {
grid-template-columns: repeat(auto-fit, minmax(6rem, 1fr));
}
.panel {
position: relative;
min-width: 0;
background: var(--ui-color-surface-panel);
box-shadow: var(--ui-shadow-inset-panel);
}
.panel::before,
.panel::after {
position: absolute;
width: 0.55rem;
height: 0.55rem;
border-color: var(--ui-color-line-strong);
content: "";
}
.panel::before {
top: 0.2rem;
right: 0.2rem;
border-top: 1px solid;
border-right: 1px solid;
}
.panel::after {
right: 0.2rem;
bottom: 0.2rem;
border-right: 1px solid;
border-bottom: 1px solid;
}
.panel__header {
border-bottom: var(--ui-border);
padding: 0.38rem 0.55rem 0.3rem;
}
.panel h2 {
font-family: var(--ui-font-display);
font-size: clamp(1.1rem, 1.62vw, 1.55rem);
font-weight: 800;
line-height: 0.9;
text-transform: uppercase;
}
.panel__body {
display: grid;
gap: 0.25rem;
padding: 0.42rem 0.55rem 0.55rem;
}
.service-panel .panel__body {
gap: 0;
padding-block: 0.18rem 0.32rem;
}
.service-panel[data-layout="grid"] .service-row {
min-height: 1.95rem;
}
.ui-button,
.icon-button {
display: inline-grid;
align-items: center;
justify-content: center;
border: var(--ui-border);
cursor: pointer;
}
.ui-button {
grid-auto-flow: column;
gap: var(--ui-space-2);
min-height: 2.5rem;
background: var(--ui-color-accent);
color: var(--ui-color-canvas);
font-size: 0.76rem;
font-weight: 850;
padding: 0 var(--ui-space-4);
text-transform: uppercase;
}
.icon-button {
width: 2.5rem;
height: 2.5rem;
place-items: center;
background: var(--ui-color-surface-footer);
color: var(--ui-color-muted);
padding: 0;
}
.theme-toggle {
display: grid;
grid-template-columns: 2.72rem minmax(0, 1fr);
align-items: center;
width: 9.75rem;
min-width: 0;
min-height: 2.18rem;
border: var(--ui-border-strong);
background: var(--ui-color-surface-module);
color: var(--ui-color-text);
cursor: pointer;
font: inherit;
font-size: 0.55rem;
font-weight: 850;
letter-spacing: 0;
padding: 0.13rem;
text-transform: uppercase;
box-shadow: var(--ui-shadow-inset-panel);
}
.theme-toggle:hover {
border-color: var(--ui-color-accent);
background: var(--ui-color-surface-raised);
}
.theme-toggle:active {
background: var(--ui-color-surface-inset);
}
.theme-toggle__label {
display: grid;
align-self: stretch;
place-items: center;
border-right: var(--ui-border);
color: var(--ui-color-muted);
font-size: 0.48rem;
font-weight: 800;
line-height: 1;
}
.theme-toggle__switch {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 0.1rem;
min-width: 0;
padding-left: 0.1rem;
}
.theme-toggle__cell {
display: grid;
grid-template-columns: auto auto;
gap: 0.2rem;
align-items: center;
justify-content: center;
min-height: 1.68rem;
border: 1px solid transparent;
color: var(--ui-color-muted);
line-height: 1;
padding: 0 0.22rem;
white-space: nowrap;
}
.theme-toggle__cell[data-active="true"] {
border-color: var(--ui-color-accent);
background: var(--ui-color-accent-soft);
color: var(--ui-color-accent);
}
.theme-toggle__cell > span {
color: currentColor;
}
.theme-toggle:hover .theme-toggle__cell[data-active="false"] {
border-color: var(--ui-color-line);
color: var(--ui-color-text);
}
.theme-toggle .icon-glyph {
width: 0.82rem;
height: 0.82rem;
border: 0;
background: transparent;
color: currentColor;
}
.theme-toggle .icon-glyph svg {
width: 0.78rem;
height: 0.78rem;
}
.icon-glyph {
display: inline-grid;
width: 1.55rem;
height: 1.55rem;
place-items: center;
border: var(--ui-border);
background: var(--ui-color-surface-icon);
color: currentColor;
line-height: 1;
}
.icon-glyph svg {
width: 0.95rem;
height: 0.95rem;
}
.icon-glyph[data-size="sm"] {
width: 1.1rem;
height: 1.1rem;
}
.icon-glyph[data-size="lg"] {
width: 2.35rem;
height: 2.35rem;
}
.system-state {
display: grid;
grid-template-columns: auto minmax(0, 1fr);
gap: var(--ui-space-3);
align-items: center;
padding: var(--ui-space-4);
}
.grid-frame {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(min(100%, 18rem), 1fr));
gap: var(--ui-space-3);
padding: var(--ui-space-3);
}
.separator {
background: var(--ui-color-line);
}
.separator[data-orientation="horizontal"] {
width: 100%;
height: 1px;
margin-block: var(--ui-space-3);
}
.corner-bracket-frame,
.diagonal-stripe-field,
.scanline-field,
.signal-trace {
display: block;
min-height: 3rem;
}
@media (max-width: 1100px) {
.telemetry-grid {
grid-template-columns: repeat(4, minmax(0, 1fr));
}
}
@media (max-width: 1180px), (max-height: 840px) {
.dashboard-frame {
grid-template-rows: auto auto auto auto;
height: auto;
min-height: 100vh;
overflow: visible;
}
.dashboard-frame__panels {
overflow: visible;
}
}
@media (max-width: 1180px) {
.dashboard-frame__header {
grid-template-columns: 1fr;
}
.dashboard-frame__panels {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
.dashboard-frame__panels .service-panel[data-layout="grid"] .panel__body {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
}
@media (max-width: 780px) {
.dashboard-frame__header,
.dashboard-frame__title,
.dashboard-header,
.dashboard-frame__modules,
.dashboard-frame__panels {
grid-template-columns: 1fr;
}
.dashboard-frame__header {
min-height: 0;
}
.dashboard-frame__title {
border-right: 0;
border-bottom: var(--ui-border);
padding-right: 0;
padding-bottom: 0.48rem;
}
.dashboard-frame h1,
.dashboard-header h1 {
white-space: normal;
}
.dashboard-frame__panels {
overflow: visible;
}
.dashboard-frame__panels .service-panel[data-layout="grid"] .panel__body {
grid-template-columns: 1fr;
}
}
@media (max-width: 700px) {
.telemetry-grid {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
}
@media (max-width: 580px) {
.service-row {
grid-template-columns: 0.28rem auto minmax(0, 1fr) auto;
min-height: 2.75rem;
}
.service-row .status-badge {
grid-column: 3;
justify-self: start;
}
.service-row__launch {
grid-column: 4;
grid-row: 1;
}
.status-strip {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
}
@media (max-width: 420px) {
.status-strip {
grid-template-columns: 1fr;
}
}

View file

@ -1,74 +0,0 @@
import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
import { join } from "node:path";
import { describe, expect, test } from "vitest";
const root = process.cwd();
const uiSourceRoot = existsSync(join(root, "packages/ui/src"))
? join(root, "packages/ui/src")
: join(root, "src");
const forbiddenTerms = [
"dimensionlab",
"dimension lab",
"vaultwarden",
"forgejo",
"grafana",
"uptime kuma",
"prometheus",
"backrest",
"open webui",
"comfyui",
"adminer",
"cockpit",
"ollama",
];
describe("UI package content boundary", () => {
test("contains the reusable dashboard component inventory", () => {
expect(existsSync(join(uiSourceRoot, "index.ts"))).toBe(true);
expect(existsSync(join(uiSourceRoot, "components/DashboardFrame.tsx"))).toBe(
true,
);
expect(existsSync(join(uiSourceRoot, "components/ThemeToggle.tsx"))).toBe(
true,
);
expect(existsSync(join(uiSourceRoot, "styles.css"))).toBe(true);
});
test("keeps environment-specific content out of reusable UI source", () => {
const source = readUiSource(uiSourceRoot).toLowerCase();
expect(forbiddenTerms.filter((term) => source.includes(term))).toEqual([]);
});
test("does not import website runtime modules", () => {
const source = readUiSource(uiSourceRoot);
expect(source).not.toMatch(
/from ["'](?:apps\/web|\$lib\/server|\$lib\/model)/,
);
expect(source).not.toContain("../web/");
});
test("keeps icon rendering driven by icon identifiers", () => {
const source = readUiSource(uiSourceRoot);
expect(source).not.toContain("@iconify-json/");
expect(source).not.toContain("/icons/");
});
});
function readUiSource(path: string): string {
if (!existsSync(path)) return "";
const stats = statSync(path);
if (stats.isFile()) {
if (path.endsWith(".test.ts") || path.endsWith(".test.tsx")) return "";
if (!/\.(tsx|ts|css)$/.test(path)) return "";
return readFileSync(path, "utf8");
}
return readdirSync(path)
.map((entry) => readUiSource(join(path, entry)))
.join("\n");
}

View file

@ -1 +0,0 @@
declare module "*.css";

View file

@ -1,132 +0,0 @@
import type { UiDashboardPreview } from "./types";
export const dashboardPreviewFixtures: Record<string, UiDashboardPreview> = {
primary: {
eyebrow: "Command Surface",
title: "Operations Console",
subtitle: "Capacity, latency, and queue health",
telemetry: [
metric("core-throughput", "Core Throughput", "mdi:server-network", 82, "ok"),
metric("edge-cache", "Edge Cache", "mdi:database-clock", 67, "neutral"),
metric("queue-depth", "Queue Depth", "mdi:tray-full", 74, "warning"),
metric("error-budget", "Error Budget", "mdi:chart-timeline-variant", 18, "danger"),
metric("build-latency", "Build Latency", "mdi:timer-sand", 23, "stale"),
metric("worker-load", "Worker Load", "mdi:cpu-64-bit", 55, "ok"),
],
modules: [
{
id: "ambient-conditions",
title: "Local Node",
value: "21.4 C",
detail: "clear window",
icon: "mdi:weather-partly-cloudy",
severity: "ok",
},
],
serviceGroups: [
{
id: "queue-workers",
title: "Queue Workers",
services: [
service("ingest", "Ingest", "Event intake pipeline", "mdi:arrow-collapse-down", "ok", "1.252 ms"),
service("scheduler", "Scheduler", "Timed job coordinator", "mdi:calendar-clock", "warning", "1.487 ms"),
service("archive", "Archive", "Cold storage transfer", "mdi:archive-arrow-down", "ok", "1.301 ms"),
],
},
{
id: "regional-nodes",
title: "Regional Nodes",
services: [
service("north", "North", "Primary traffic cell", "mdi:access-point", "ok", "899 ms"),
service("west", "West", "Replica traffic cell", "mdi:access-point-network", "neutral", "1.118 ms"),
service("south", "South", "Maintenance window", "mdi:wrench-clock", "stale", "paused"),
],
},
{
id: "control-plane",
title: "Control Plane",
services: [
service("identity", "Identity", "Access token exchange", "mdi:account-key", "ok", "721 ms"),
service("policy", "Policy", "Rules evaluation", "mdi:shield-check", "ok", "812 ms"),
service("audit", "Audit", "Event ledger writer", "mdi:text-box-search", "warning", "1.442 ms"),
],
},
],
statusItems: [
{ id: "system", label: "System", value: "Nominal", severity: "ok" },
{ id: "sync", label: "Last Sync", value: "2 minutes ago", severity: "stale" },
{ id: "uptime", label: "Uptime", value: "14d 08h", severity: "neutral" },
{ id: "refresh", label: "Refresh", value: "15s", severity: "neutral" },
],
statusStripId: "dashboard-status",
},
secondary: {
eyebrow: "Support Surface",
title: "Support Desk",
subtitle: "Response, routing, and regional health",
telemetry: [
metric("response-window", "Response Window", "mdi:headset", 41, "ok"),
metric("ticket-load", "Ticket Load", "mdi:ticket-confirmation", 68, "warning"),
metric("handoff-drift", "Handoff Drift", "mdi:swap-horizontal", 11, "neutral"),
metric("coverage-gap", "Coverage Gap", "mdi:map-marker-alert", 7, "danger"),
metric("routing-warmup", "Routing Warmup", "mdi:progress-clock", 0, "loading"),
],
modules: [
{
id: "shift-state",
title: "Shift State",
value: "covered",
detail: "static fixture",
icon: "mdi:clipboard-check-outline",
severity: "ok",
},
],
serviceGroups: [
{
id: "regional-nodes",
title: "Regional Nodes",
services: [
service("desk-a", "Desk A", "Frontline queue", "mdi:monitor-dashboard", "ok", "843 ms"),
service("desk-b", "Desk B", "Escalation queue", "mdi:monitor-star", "warning", "1.204 ms"),
service("desk-c", "Desk C", "Overflow queue", "mdi:monitor-off", "unavailable", "offline"),
],
},
],
statusItems: [
{ id: "desk", label: "Desk", value: "Covered", severity: "ok" },
{ id: "handoff", label: "Handoff", value: "Pending", severity: "warning" },
{ id: "routing", label: "Routing", value: "Manual", severity: "neutral" },
],
statusStripId: "support-status",
},
};
function metric(
id: string,
label: string,
icon: string,
progress: number,
severity: UiDashboardPreview["telemetry"][number]["severity"],
) {
return {
id,
label,
icon,
progress,
severity,
value: { kind: "percent" as const, value: progress },
detail: "static fixture",
sparkline: [12, 18, 15, 28, 24, progress],
};
}
function service(
id: string,
label: string,
description: string,
icon: string,
severity: UiDashboardPreview["serviceGroups"][number]["services"][number]["severity"],
detail: string,
) {
return { id, label, description, icon, severity, detail };
}

View file

@ -1,43 +0,0 @@
import type { UiMetricValue } from "./types";
const byteUnits = ["B", "KB", "MB", "GB", "TB"];
export function formatMetricValue(metric: UiMetricValue): string {
if (metric.kind === "text") return String(metric.value);
const value = typeof metric.value === "number" ? metric.value : Number(metric.value);
const precision = metric.precision ?? inferPrecision(value);
const unit = metric.unit ?? defaultUnit(metric.kind);
if (metric.kind === "bytes") return formatBytes(value, precision);
if (metric.kind === "percent") return `${value.toFixed(precision)}%`;
if (metric.kind === "temperature") return `${value.toFixed(precision)}°C`;
return `${value.toFixed(precision)}${unit ? ` ${unit}` : ""}`;
}
export function clampPercent(value = 0): number {
if (!Number.isFinite(value)) return 0;
return Math.max(0, Math.min(100, value));
}
function defaultUnit(kind: UiMetricValue["kind"]): string {
if (kind === "percent") return "%";
if (kind === "temperature") return "C";
if (kind === "latency") return "ms";
return "";
}
function inferPrecision(value: number): number {
return Number.isInteger(value) ? 0 : 1;
}
function formatBytes(value: number, precision: number): string {
let size = value;
let index = 0;
while (size >= 1024 && index < byteUnits.length - 1) {
size /= 1024;
index += 1;
}
return `${size.toFixed(precision)} ${byteUnits[index]}`;
}

View file

@ -1,50 +0,0 @@
export { Badge } from "./components/Badge";
export { Button } from "./components/Button";
export { CornerBracketFrame } from "./components/CornerBracketFrame";
export { DashboardHeader } from "./components/DashboardHeader";
export { DashboardFrame } from "./components/DashboardFrame";
export { DiagonalStripeField } from "./components/DiagonalStripeField";
export { FooterCell } from "./components/FooterCell";
export { FooterStatusCell } from "./components/FooterStatusCell";
export { GridFrame } from "./components/GridFrame";
export { IconGlyph } from "./components/IconGlyph";
export { IconButton } from "./components/IconButton";
export { LineChart } from "./components/LineChart";
export { ModuleCard } from "./components/ModuleCard";
export { Panel } from "./components/Panel";
export { ProgressMeter } from "./components/ProgressMeter";
export { Separator } from "./components/Separator";
export { ServiceGroupPanel } from "./components/ServiceGroupPanel";
export { ServicePanel } from "./components/ServicePanel";
export { ServiceRow } from "./components/ServiceRow";
export { ScanlineField } from "./components/ScanlineField";
export { SignalTrace } from "./components/SignalTrace";
export { Sparkline } from "./components/Sparkline";
export { StatusBadge } from "./components/StatusBadge";
export { StatusStrip } from "./components/StatusStrip";
export { SystemState } from "./components/SystemState";
export { TelemetryCard } from "./components/TelemetryCard";
export { TelemetryGrid } from "./components/TelemetryGrid";
export { TelemetryStrip } from "./components/TelemetryStrip";
export { ThemeToggle } from "./components/ThemeToggle";
export { WeatherModule } from "./components/WeatherModule";
export { dashboardPreviewFixtures } from "./fixtures";
export {
getNextUiTheme,
isUiTheme,
persistUiTheme,
resolveInitialUiTheme,
UI_THEME_STORAGE_KEY,
} from "./theme";
export type {
UiDashboardPreview,
UiLink,
UiMetricValue,
UiModuleBlock,
UiServiceGroup,
UiServiceRow,
UiSeverity,
UiStatusItem,
UiTelemetryCard,
} from "./types";
export type { UiTheme } from "./theme";

View file

@ -1,76 +0,0 @@
import * as React from "react"
import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "../utils"
const alertVariants = cva(
"group/alert relative grid w-full gap-0.5 rounded-lg border px-2.5 py-2 text-left text-sm has-data-[slot=alert-action]:relative has-data-[slot=alert-action]:pr-18 has-[>svg]:grid-cols-[auto_1fr] has-[>svg]:gap-x-2 *:[svg]:row-span-2 *:[svg]:translate-y-0.5 *:[svg]:text-current *:[svg:not([class*='size-'])]:size-4",
{
variants: {
variant: {
default: "bg-card text-card-foreground",
destructive:
"bg-card text-destructive *:data-[slot=alert-description]:text-destructive/90 *:[svg]:text-current",
},
},
defaultVariants: {
variant: "default",
},
}
)
function Alert({
className,
variant,
...props
}: React.ComponentProps<"div"> & VariantProps<typeof alertVariants>) {
return (
<div
data-slot="alert"
role="alert"
className={cn(alertVariants({ variant }), className)}
{...props}
/>
)
}
function AlertTitle({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="alert-title"
className={cn(
"font-medium group-has-[>svg]/alert:col-start-2 [&_a]:underline [&_a]:underline-offset-3 [&_a]:hover:text-foreground",
className
)}
{...props}
/>
)
}
function AlertDescription({
className,
...props
}: React.ComponentProps<"div">) {
return (
<div
data-slot="alert-description"
className={cn(
"text-sm text-balance text-muted-foreground md:text-pretty [&_a]:underline [&_a]:underline-offset-3 [&_a]:hover:text-foreground [&_p:not(:last-child)]:mb-4",
className
)}
{...props}
/>
)
}
function AlertAction({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="alert-action"
className={cn("absolute top-2 right-2", className)}
{...props}
/>
)
}
export { Alert, AlertTitle, AlertDescription, AlertAction }

View file

@ -1,49 +0,0 @@
import * as React from "react"
import { cva, type VariantProps } from "class-variance-authority"
import { Slot } from "radix-ui"
import { cn } from "../utils"
const badgeVariants = cva(
"group/badge inline-flex h-5 w-fit shrink-0 items-center justify-center gap-1 overflow-hidden rounded-4xl border border-transparent px-2 py-0.5 text-xs font-medium whitespace-nowrap transition-all focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 [&>svg]:pointer-events-none [&>svg]:size-3!",
{
variants: {
variant: {
default: "bg-primary text-primary-foreground [a]:hover:bg-primary/80",
secondary:
"bg-secondary text-secondary-foreground [a]:hover:bg-secondary/80",
destructive:
"bg-destructive/10 text-destructive focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:focus-visible:ring-destructive/40 [a]:hover:bg-destructive/20",
outline:
"border-border text-foreground [a]:hover:bg-muted [a]:hover:text-muted-foreground",
ghost:
"hover:bg-muted hover:text-muted-foreground dark:hover:bg-muted/50",
link: "text-primary underline-offset-4 hover:underline",
},
},
defaultVariants: {
variant: "default",
},
}
)
function Badge({
className,
variant = "default",
asChild = false,
...props
}: React.ComponentProps<"span"> &
VariantProps<typeof badgeVariants> & { asChild?: boolean }) {
const Comp = asChild ? Slot.Root : "span"
return (
<Comp
data-slot="badge"
data-variant={variant}
className={cn(badgeVariants({ variant }), className)}
{...props}
/>
)
}
export { Badge, badgeVariants }

View file

@ -1,67 +0,0 @@
import * as React from "react"
import { cva, type VariantProps } from "class-variance-authority"
import { Slot } from "radix-ui"
import { cn } from "../utils"
const buttonVariants = cva(
"group/button inline-flex shrink-0 items-center justify-center rounded-lg border border-transparent bg-clip-padding text-sm font-medium whitespace-nowrap transition-all outline-none select-none focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 active:not-aria-[haspopup]:translate-y-px disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
{
variants: {
variant: {
default: "bg-primary text-primary-foreground hover:bg-primary/80",
outline:
"border-border bg-background hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:border-input dark:bg-input/30 dark:hover:bg-input/50",
secondary:
"bg-secondary text-secondary-foreground hover:bg-[color-mix(in_oklch,var(--secondary),var(--foreground)_5%)] aria-expanded:bg-secondary aria-expanded:text-secondary-foreground",
ghost:
"hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:hover:bg-muted/50",
destructive:
"bg-destructive/10 text-destructive hover:bg-destructive/20 focus-visible:border-destructive/40 focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:hover:bg-destructive/30 dark:focus-visible:ring-destructive/40",
link: "text-primary underline-offset-4 hover:underline",
},
size: {
default:
"h-8 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2",
xs: "h-6 gap-1 rounded-[min(var(--radius-md),10px)] px-2 text-xs in-data-[slot=button-group]:rounded-lg has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3",
sm: "h-7 gap-1 rounded-[min(var(--radius-md),12px)] px-2.5 text-[0.8rem] in-data-[slot=button-group]:rounded-lg has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3.5",
lg: "h-9 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2",
icon: "size-8",
"icon-xs":
"size-6 rounded-[min(var(--radius-md),10px)] in-data-[slot=button-group]:rounded-lg [&_svg:not([class*='size-'])]:size-3",
"icon-sm":
"size-7 rounded-[min(var(--radius-md),12px)] in-data-[slot=button-group]:rounded-lg",
"icon-lg": "size-9",
},
},
defaultVariants: {
variant: "default",
size: "default",
},
}
)
function Button({
className,
variant = "default",
size = "default",
asChild = false,
...props
}: React.ComponentProps<"button"> &
VariantProps<typeof buttonVariants> & {
asChild?: boolean
}) {
const Comp = asChild ? Slot.Root : "button"
return (
<Comp
data-slot="button"
data-variant={variant}
data-size={size}
className={cn(buttonVariants({ variant, size, className }))}
{...props}
/>
)
}
export { Button, buttonVariants }

View file

@ -1,103 +0,0 @@
import * as React from "react"
import { cn } from "../utils"
function Card({
className,
size = "default",
...props
}: React.ComponentProps<"div"> & { size?: "default" | "sm" }) {
return (
<div
data-slot="card"
data-size={size}
className={cn(
"group/card flex flex-col gap-(--card-spacing) overflow-hidden rounded-xl bg-card py-(--card-spacing) text-sm text-card-foreground ring-1 ring-foreground/10 [--card-spacing:--spacing(4)] has-data-[slot=card-footer]:pb-0 has-[>img:first-child]:pt-0 data-[size=sm]:[--card-spacing:--spacing(3)] data-[size=sm]:has-data-[slot=card-footer]:pb-0 *:[img:first-child]:rounded-t-xl *:[img:last-child]:rounded-b-xl",
className
)}
{...props}
/>
)
}
function CardHeader({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="card-header"
className={cn(
"group/card-header @container/card-header grid auto-rows-min items-start gap-1 rounded-t-xl px-(--card-spacing) has-data-[slot=card-action]:grid-cols-[1fr_auto] has-data-[slot=card-description]:grid-rows-[auto_auto] [.border-b]:pb-(--card-spacing)",
className
)}
{...props}
/>
)
}
function CardTitle({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="card-title"
className={cn(
"font-heading text-base leading-snug font-medium group-data-[size=sm]/card:text-sm",
className
)}
{...props}
/>
)
}
function CardDescription({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="card-description"
className={cn("text-sm text-muted-foreground", className)}
{...props}
/>
)
}
function CardAction({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="card-action"
className={cn(
"col-start-2 row-span-2 row-start-1 self-start justify-self-end",
className
)}
{...props}
/>
)
}
function CardContent({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="card-content"
className={cn("px-(--card-spacing)", className)}
{...props}
/>
)
}
function CardFooter({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="card-footer"
className={cn(
"flex items-center rounded-b-xl border-t bg-muted/50 p-(--card-spacing)",
className
)}
{...props}
/>
)
}
export {
Card,
CardHeader,
CardFooter,
CardTitle,
CardAction,
CardDescription,
CardContent,
}

Some files were not shown because too many files have changed in this diff Show more