diff --git a/.containerignore b/.containerignore
deleted file mode 100644
index 1812091..0000000
--- a/.containerignore
+++ /dev/null
@@ -1,22 +0,0 @@
-.git
-.svelte-kit
-build
-coverage
-data
-dist
-node_modules
-out
-playwright-report
-storybook-static
-test-results
-.env
-.env.*
-apps/*/.turbo
-apps/*/build
-apps/*/data
-apps/*/dist
-apps/*/playwright-report
-apps/*/test-results
-packages/*/.turbo
-packages/*/dist
-packages/*/storybook-static
diff --git a/.forgejo/workflows/dimensionlab-website.yml b/.forgejo/workflows/dimensionlab-website.yml
deleted file mode 100644
index 198a8aa..0000000
--- a/.forgejo/workflows/dimensionlab-website.yml
+++ /dev/null
@@ -1,85 +0,0 @@
-name: Dimension Lab website
-
-on:
- pull_request:
- types:
- - opened
- - synchronize
- - reopened
- push:
- branches:
- - main
- workflow_dispatch:
-
-concurrency:
- group: dimensionlab-website-${{ github.ref }}
- cancel-in-progress: ${{ github.ref != 'refs/heads/main' }}
-
-jobs:
- ci:
- runs-on: docker
- timeout-minutes: 30
- steps:
- - name: Checkout
- uses: https://data.forgejo.org/actions/checkout@v4
- with:
- fetch-depth: 0
- submodules: false
-
- - name: Initialize submodules
- run: |
- git config --global url."https://git.dimensionlab.net/".insteadOf "ssh://git@git.dimensionlab.net/"
- git submodule update --init --recursive
-
- - name: Install Bun
- run: |
- curl -fsSL https://bun.sh/install | bash -s "bun-v1.3.14"
- "$HOME/.bun/bin/bun" --version
-
- - name: Check, test, and build
- run: |
- export BUN_INSTALL="$HOME/.bun"
- export PATH="$BUN_INSTALL/bin:$PATH"
- bun install --frozen-lockfile
- bun run check
- bun run test
- bun run build
-
- deploy:
- needs: ci
- if: github.event_name == 'push' && github.ref == 'refs/heads/main'
- runs-on: deploy
- timeout-minutes: 30
- steps:
- - name: Checkout
- run: |
- if [ -d .git ]; then
- git remote set-url origin git@git.dimensionlab.net:vince/dimensionlab-website.git
- else
- git init
- git remote add origin git@git.dimensionlab.net:vince/dimensionlab-website.git
- fi
- git fetch --force --prune --depth=1 origin "$GITHUB_SHA"
- git checkout --force --detach "$GITHUB_SHA"
- git clean -ffdx
-
- - name: Initialize submodules
- run: |
- git config --global url."https://git.dimensionlab.net/".insteadOf "ssh://git@git.dimensionlab.net/"
- git submodule update --init --recursive
-
- - name: Verify Podman deployment socket
- run: |
- command -v podman
- command -v systemctl
- unit="$(timeout 15s podman inspect dimensionlab-website --format '{{ index .Config.Labels "PODMAN_SYSTEMD_UNIT" }}')"
- test "$unit" = "dimensionlab-website.service"
-
- - name: Deploy production website
- env:
- DEPLOY_CONTAINER_CLI: podman
- DEPLOY_EVENT_NAME: ${{ github.event_name }}
- DEPLOY_REF: ${{ github.ref }}
- DEPLOY_RESTART_STRATEGY: quadlet-container
- DEPLOY_SHA: ${{ github.sha }}
- run: scripts/deploy-dimensionlab-website.sh
diff --git a/.gitignore b/.gitignore
index eed0de1..7d8d6c4 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,12 +1,8 @@
node_modules/
-out/
.svelte-kit/
build/
dist/
.vite/
-.turbo/
-apps/*/.turbo/
-packages/*/.turbo/
.env
.env.*
@@ -14,8 +10,6 @@ packages/*/.turbo/
data/*.sqlite
data/*.sqlite-*
-apps/*/data/*.sqlite
-apps/*/data/*.sqlite-*
coverage/
playwright-report/
test-results/
diff --git a/.gitmodules b/.gitmodules
deleted file mode 100644
index 32d1c14..0000000
--- a/.gitmodules
+++ /dev/null
@@ -1,4 +0,0 @@
-[submodule "packages/ui"]
- path = packages/ui
- url = ssh://git@git.dimensionlab.net/vince/dimensionlab-ui.git
- branch = main
diff --git a/.storybook/main.ts b/.storybook/main.ts
new file mode 100644
index 0000000..3dd8a72
--- /dev/null
+++ b/.storybook/main.ts
@@ -0,0 +1,19 @@
+import type { StorybookConfig } from "@storybook/sveltekit";
+
+const config: StorybookConfig = {
+ stories: ["../src/**/*.stories.@(js|ts|svelte)"],
+ addons: [
+ "@storybook/addon-svelte-csf",
+ "@storybook/addon-a11y",
+ "@storybook/addon-vitest",
+ ],
+ framework: {
+ name: "@storybook/sveltekit",
+ options: {},
+ },
+ docs: {
+ autodocs: "tag",
+ },
+};
+
+export default config;
diff --git a/.storybook/preview.ts b/.storybook/preview.ts
new file mode 100644
index 0000000..33f01f6
--- /dev/null
+++ b/.storybook/preview.ts
@@ -0,0 +1,23 @@
+import "../src/app.css";
+import type { Preview } from "@storybook/sveltekit";
+
+const preview: Preview = {
+ parameters: {
+ backgrounds: {
+ default: "canvas",
+ values: [
+ { name: "canvas", value: "#020302" },
+ { name: "raised", value: "#0b0d0c" },
+ ],
+ },
+ controls: {
+ matchers: {
+ color: /(background|color)$/i,
+ date: /Date$/i,
+ },
+ },
+ layout: "fullscreen",
+ },
+};
+
+export default preview;
diff --git a/README.md b/README.md
index 85177c5..c49bbd4 100644
--- a/README.md
+++ b/README.md
@@ -1,30 +1,15 @@
# Dimension Lab Website
-Turbo/Bun workspace for the Dimension Lab system overview dashboard and its
-reusable React component library.
+Standalone SvelteKit runtime for the Dimension Lab system overview dashboard.
This project is not a Homepage customization and does not depend on Homepage
runtime, frontend code, or configuration. The dashboard will be model-driven:
-the reusable UI package stays content-free, the reusable dashboard model
-package owns schema and validation, and environment-specific data lives in
-validated dashboard model state inside the web app.
-
-## Workspace Layout
-
-- `apps/web`: Vite React website, Bun API server, model fixtures, Drizzle
- persistence, Playwright e2e checks, and container build.
-- `packages/dashboard-model`: reusable dashboard schema, validation, and
- generic model fixtures shared by apps and tooling.
-- `packages/ui`: Git submodule for the reusable dashboard React components,
- design tokens, shadcn/radix primitives, generic fixtures, and Storybook.
- Component source is grouped under `foundation`, `frames`, `operations`, and
- `telemetry` domains.
-- `docs/superpowers`: migration specs and execution plans used for this repo.
+the reusable renderer stays content-free, while environment-specific data lives
+in validated dashboard model state.
## Development
```sh
-git submodule update --init --recursive
bun install
bun run dev
```
@@ -33,19 +18,15 @@ This MVP uses Drizzle with Bun SQLite for local file-backed persistence.
## Scripts
-- `bun run dev`: start the web app dev runtime through Turbo.
-- `bun run check`: run TypeScript checks in all workspaces.
-- `bun run test`: run the unit test stage in all workspaces.
-- `bun run test:unit`: run Vitest explicitly as the unit test stage.
-- `bun run test:e2e`: build and run Playwright browser smoke and QA checks.
-- `bun run test:qa`: run the release gate through Turbo across check, unit,
- build, Storybook, and e2e tasks.
-- `bun run build`: build the UI package, production website, and Bun server.
-- `bun run preview`: preview the production web build.
-- `bun run storybook`: start the UI package component explorer on port 6006.
-- `bun run build-storybook`: build the UI package static Storybook artifact.
-- `bun run db:generate`: generate web app Drizzle migrations.
-- `bun run db:check`: validate web app migration consistency.
+- `bun run dev`: start the local development server.
+- `bun run check`: run Svelte and TypeScript checks.
+- `bun run test`: run Vitest.
+- `bun run build`: build the production app.
+- `bun run preview`: preview the production build.
+- `bun run storybook`: start the component explorer on port 6006.
+- `bun run build-storybook`: build the static Storybook review artifact.
+- `bun run db:generate`: generate Drizzle migrations from the server schema.
+- `bun run db:check`: validate migration consistency.
## Persistence
@@ -56,14 +37,14 @@ URL is:
DATABASE_URL=file:./data/dimensionlab.sqlite
```
-SQLite files under `data/` and `apps/*/data/` are ignored. Drizzle schema lives
-in `apps/web/src/lib/server/db/schema.ts`; tracked migrations live in
-`apps/web/drizzle/`. Runtime startup applies the checked-in dashboard migrations
-before reads or writes. If the app is launched from outside the web app tree,
-set `DASHBOARD_MIGRATIONS_DIR` to the tracked migrations directory. The current
-driver is `bun:sqlite`, which keeps this repo installable in the Bun workflow.
-The store boundary is isolated so a later Postgres driver can replace the
-SQLite connection without changing the dashboard model or renderer.
+SQLite files under `data/` are ignored. Drizzle schema lives in
+`src/lib/server/db/schema.ts`; tracked migrations live in `drizzle/`. Runtime
+startup applies the checked-in dashboard migrations before reads or writes. If
+the app is launched from outside the repo tree, set `DASHBOARD_MIGRATIONS_DIR`
+to the tracked migrations directory. The current driver is `bun:sqlite`, which
+keeps this repo installable in the Bun workflow. The store boundary is isolated
+so a later Postgres driver can replace the SQLite connection without changing
+the dashboard model or renderer.
Stored dashboard documents pass through a version migration boundary before
reads or writes; the MVP supports `dashboard.v1` and fails unsupported versions
with an explicit migration error.
@@ -71,117 +52,20 @@ with an explicit migration error.
## Seed Data
The initial Dimension Lab dashboard lives in
-`apps/web/src/lib/dashboard-seed/dimensionlab.ts` as validated model data. It
-includes the first-screen telemetry, service groups, status strip, weather
-module, Iconify icon identifiers, links, and datasource references. Values that
-are not live yet are labeled as fallback values in the data so later datasource
-adapters can replace them without changing presentation components.
+`src/lib/model/fixtures/dimensionlab.ts` as validated model data. It includes
+the first-screen telemetry, service groups, status strip, weather module,
+Iconify icon identifiers, links, and datasource references. Values that are not
+live yet are labeled as fallback values in the data so later datasource adapters
+can replace them without changing presentation components.
## Runtime Shape
-The browser app in `apps/web` is built with Vite and React. Local development
-starts Vite for HMR and a loopback Bun API server for `/api/*` routes.
-Production uses a small Bun HTTP server at `apps/web/build/index.js` to serve
-the Vite `apps/web/dist/` assets and JSON API routes. The current persistence
-runtime is Bun because the MVP SQLite driver is `bun:sqlite`.
+The SvelteKit build uses the Node adapter, and the current persistence runtime
+is Bun because the MVP SQLite driver is `bun:sqlite`. Later issues add the
+seed data expansion and rendering pipeline.
## Storybook
-Storybook lives with `packages/ui` and covers the reusable UI components with
-generic fixtures only. Stories must not import environment-specific dashboard
-content; the presentation layer accepts labels, values, icons, status, and links
-through typed props.
-
-## MVP QA Gate
-
-Install the Chromium browser once before running e2e checks locally:
-
-```sh
-bunx playwright install chromium
-```
-
-Run the CI-ready release gate with:
-
-```sh
-bun run test:qa
-```
-
-The gate runs TypeScript checks, Vitest coverage for model,
-persistence, renderer, datasource mocks, and presentation boundaries, the
-production build, the static Storybook build, and Playwright desktop/mobile
-smoke checks against the built adapter output. Turbo owns the release task
-graph; app package scripts stay as leaf commands and do not re-run the QA
-pipeline internally. Playwright also performs
-baseline screenshot checks, keyboard navigation checks, reduced-motion checks,
-landmark checks, and axe accessibility checks against the real model-driven
-route.
-
-Playwright uses an isolated SQLite database per run unless
-`PLAYWRIGHT_DATABASE_URL` is set explicitly.
-
-Presentation code is checked for Dimension Lab content leakage. Environment
-specific labels, links, icon names, fallback values, and datasource references
-belong in validated model data, not reusable components.
-
-## Deployment Notes
-
-The production build emits Vite client assets under `apps/web/dist/` and a Bun
-server entry at `apps/web/build/index.js`. A minimal deployment flow is:
-
-```sh
-git submodule update --init --recursive
-bun install --frozen-lockfile
-bun run build
-cd apps/web
-DATABASE_URL=file:/data/dimensionlab.sqlite HOST=0.0.0.0 PORT=3000 bun build/index.js
-```
-
-Mount `/data` or set `DATABASE_URL` to another persistent SQLite path. If the
-process starts outside the repository root, set `DASHBOARD_MIGRATIONS_DIR` to
-the checked-in `apps/web/drizzle/` directory so startup migrations can run.
-
-### Internal Container
-
-The checked-in `apps/web/Containerfile` runs
-`turbo prune @dimensionlab/web --docker`, installs the pruned manifest set, and
-builds the React client plus Bun server from the pruned workspace source. For the
-Dimension Lab internal host, run it behind Caddy on a loopback port and mount
-persistent state at `/data`:
-
-```sh
-podman build -f apps/web/Containerfile -t localhost/dimensionlab-website:latest .
-podman run --rm \
- --publish 127.0.0.1:25341:3000 \
- --volume "$HOME/containers/dimensionlab-website/data:/data:Z" \
- --env-file "$HOME/containers/dimensionlab-website/dimensionlab-website.env" \
- localhost/dimensionlab-website:latest
-```
-
-The env file must provide `AGENT_CONFIG_TOKEN`. Runtime defaults inside the
-image set `HOST=0.0.0.0`, `PORT=3000`,
-`DATABASE_URL=file:/data/dimensionlab.sqlite`, and
-`DASHBOARD_MIGRATIONS_DIR=/repo/apps/web/drizzle`.
-
-### Forgejo Actions Deployment
-
-Merges to `main` run `.forgejo/workflows/dimensionlab-website.yml`. Pull
-requests run check, test, and build only; the deploy job is guarded to run only
-for `push` events on `refs/heads/main`.
-
-The workflow uses two runner classes. Pull request CI runs on the containerized
-`docker` runner. Production deployment runs on a separate host runner with the
-`deploy:host` label so the guarded deploy script can use the user's rootless
-`podman` and `systemctl --user` commands directly. The deploy job uses a
-shell-only `git fetch` checkout so the host runner does not need a Node runtime
-for checkout actions.
-
-```yaml
-runner:
- labels:
- - deploy:host
-```
-
-The deploy job also performs a host preflight against the
-`dimensionlab-website.service` Podman label before it builds or restarts the
-production container. Do not give the general pull request runner deployment
-socket access; keep deploy privileges on the dedicated `deploy` runner.
+Storybook covers the reusable UI components with generic fixtures only. Stories
+must not import environment-specific dashboard content; the presentation layer
+accepts labels, values, icons, status, and links through typed props.
diff --git a/apps/web/Containerfile b/apps/web/Containerfile
deleted file mode 100644
index 32387a4..0000000
--- a/apps/web/Containerfile
+++ /dev/null
@@ -1,39 +0,0 @@
-FROM docker.io/oven/bun:1.3.14 AS base
-
-WORKDIR /repo
-ENV PATH=/repo/apps/web/node_modules/.bin:/repo/packages/dashboard-model/node_modules/.bin:/repo/packages/ui/node_modules/.bin:/repo/node_modules/.bin:$PATH
-
-FROM base AS pruner
-
-COPY . .
-RUN bunx turbo prune @dimensionlab/web --docker
-
-FROM base AS deps
-
-COPY --from=pruner /repo/out/json/ ./
-RUN bun install --frozen-lockfile --ignore-scripts
-
-FROM deps AS build
-
-COPY --from=pruner /repo/out/full/ ./
-COPY --from=pruner /repo/tsconfig.base.json /repo/tsconfig.json ./
-RUN bun run build
-
-FROM base AS runtime
-
-WORKDIR /repo/apps/web
-ENV NODE_ENV=production
-ENV HOST=0.0.0.0
-ENV PORT=3000
-ENV DATABASE_URL=file:/data/dimensionlab.sqlite
-ENV DASHBOARD_MIGRATIONS_DIR=/repo/apps/web/drizzle
-
-COPY --from=build /repo/apps/web/build ./build
-COPY --from=build /repo/apps/web/dist ./dist
-COPY --from=build /repo/apps/web/drizzle ./drizzle
-
-RUN mkdir -p /data
-VOLUME ["/data"]
-EXPOSE 3000
-
-CMD ["bun", "build/index.js"]
diff --git a/apps/web/index.html b/apps/web/index.html
deleted file mode 100644
index 75dae71..0000000
--- a/apps/web/index.html
+++ /dev/null
@@ -1,22 +0,0 @@
-
-
-
-
-
-
- Dimension Lab
-
-
-
-
-
-
-
diff --git a/apps/web/package.json b/apps/web/package.json
deleted file mode 100644
index 015fc55..0000000
--- a/apps/web/package.json
+++ /dev/null
@@ -1,51 +0,0 @@
-{
- "name": "@dimensionlab/web",
- "version": "0.0.1",
- "private": true,
- "type": "module",
- "scripts": {
- "dev": "bun src/server/dev.ts",
- "build": "rm -rf build && vite build && bun build src/server/index.ts --target bun --outdir build",
- "preview": "HOST=0.0.0.0 PORT=4173 bun build/index.js",
- "check": "tsc --noEmit",
- "test": "bun --bun vitest run",
- "test:unit": "bun --bun vitest run",
- "test:e2e": "env -u NO_COLOR playwright test",
- "db:generate": "drizzle-kit generate",
- "db:check": "drizzle-kit check"
- },
- "dependencies": {
- "@dimensionlab/dashboard-model": "workspace:*",
- "@dimensionlab/ui": "workspace:*",
- "@sinclair/typebox": "^0.34.49",
- "ajv": "^8.20.0",
- "ajv-formats": "^3.0.1",
- "drizzle-orm": "^0.45.2",
- "react": "^19.2.7",
- "react-dom": "^19.2.7",
- "tw-animate-css": "^1.4.0"
- },
- "devDependencies": {
- "@axe-core/playwright": "^4.11.3",
- "@playwright/test": "^1.61.0",
- "@tailwindcss/vite": "^4.3.1",
- "@types/bun": "^1.3.14",
- "@types/node": "^25.9.3",
- "@types/react": "^19.2.17",
- "@types/react-dom": "^19.2.3",
- "@vitejs/plugin-react": "^6.0.2",
- "bun-types": "^1.3.14",
- "drizzle-kit": "^0.31.10",
- "msw": "^2.14.6",
- "shadcn": "^4.11.0",
- "tailwindcss": "^4.3.1",
- "typescript": "^6.0.3",
- "vite": "^8.0.16",
- "vitest": "^4.1.9"
- },
- "msw": {
- "workerDirectory": [
- "static"
- ]
- }
-}
diff --git a/apps/web/playwright.config.ts b/apps/web/playwright.config.ts
deleted file mode 100644
index ac0fe5e..0000000
--- a/apps/web/playwright.config.ts
+++ /dev/null
@@ -1,51 +0,0 @@
-import { defineConfig, devices } from "@playwright/test";
-
-const port = Number(process.env.PLAYWRIGHT_PORT || 4173);
-const baseURL = `http://127.0.0.1:${port}`;
-const storybookPort = Number(process.env.PLAYWRIGHT_STORYBOOK_PORT || 6007);
-const storybookURL = `http://127.0.0.1:${storybookPort}`;
-const databaseUrl =
- process.env.PLAYWRIGHT_DATABASE_URL ||
- `file:./data/playwright-${process.pid}-${Date.now()}.sqlite`;
-
-export default defineConfig({
- testDir: "tests/e2e",
- fullyParallel: true,
- forbidOnly: !!process.env.CI,
- retries: process.env.CI ? 2 : 0,
- reporter: process.env.CI ? [["github"], ["list"]] : "list",
- use: {
- baseURL,
- trace: "retain-on-failure",
- screenshot: "only-on-failure",
- },
- webServer: [
- {
- command: `DISABLE_LIVE_DATASOURCES=1 DATABASE_URL=${databaseUrl} HOST=127.0.0.1 PORT=${port} bun build/index.js`,
- url: baseURL,
- reuseExistingServer: false,
- timeout: 120_000,
- },
- {
- command: `cd ../.. && STORYBOOK_STATIC_PORT=${storybookPort} bun apps/web/tests/e2e/storybook-server.ts`,
- url: storybookURL,
- reuseExistingServer: false,
- timeout: 120_000,
- },
- ],
- projects: [
- {
- name: "chromium-desktop",
- use: {
- ...devices["Desktop Chrome"],
- viewport: { width: 1440, height: 1000 },
- },
- },
- {
- name: "chromium-mobile",
- use: {
- ...devices["Pixel 7"],
- },
- },
- ],
-});
diff --git a/apps/web/src/App.test.tsx b/apps/web/src/App.test.tsx
deleted file mode 100644
index c1add67..0000000
--- a/apps/web/src/App.test.tsx
+++ /dev/null
@@ -1,238 +0,0 @@
-import { renderToString } from "react-dom/server";
-import { describe, expect, test } from "vitest";
-import { genericDashboardFixture } from "@dimensionlab/dashboard-model/fixtures";
-import { dimensionLabDashboardFixture } from "$lib/dashboard-seed/dimensionlab";
-import {
- AppStateView,
- dashboardTileMatchKey,
- dashboardHydrationTiles,
- restoreDashboardTileSnapshots,
-} from "./App";
-
-describe("React app dashboard state view", () => {
- test("renders loading dashboard state", () => {
- const html = renderToString(
- ,
- );
-
- expect(html).toContain("Loading Dashboard");
- expect(html).toContain("Fetching active model");
- });
-
- test("renders an accessible theme toggle with the active theme", () => {
- const html = renderToString(
- undefined}
- />,
- );
-
- expect(html).toContain('data-ui-theme-toggle="true"');
- expect(html).toContain('data-ui-theme-current="dark"');
- expect(html).toContain('aria-label="Light theme"');
- expect(html).toContain('aria-pressed="false"');
- expect(html).toContain("Theme");
- expect(html).toContain("Dark");
- expect(html).toContain("Light");
- });
-
- test("renders the dashboard shell while individual items hydrate", () => {
- const html = renderToString(
- ,
- );
-
- 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" }),
- );
- });
-});
diff --git a/apps/web/src/App.tsx b/apps/web/src/App.tsx
deleted file mode 100644
index 403bef9..0000000
--- a/apps/web/src/App.tsx
+++ /dev/null
@@ -1,875 +0,0 @@
-import { useEffect, useState } from "react";
-import type {
- DashboardDocument,
- DashboardModule,
- ServiceEntry,
- StatusItem,
- TelemetryCard,
-} from "@dimensionlab/dashboard-model";
-import type { DashboardRuntimeState } from "$lib/server/dashboard";
-import {
- attachDashboardRefreshLifecycle,
- collectVisibleDashboardModelIds,
- createDashboardPerformanceMarks,
- createDashboardRefreshDelay,
- createDashboardTileSnapshotStore,
- createDashboardTileBackoff,
- createDashboardRequestAborter,
- isPersistableDashboardTileSnapshot,
- runViewportAwareDashboardHydrationQueue,
- shouldPauseDashboardRefresh,
- subscribeToDashboardTileEvents,
- waitForDashboardHydrationIdle,
- type DashboardIntersectionObserverFactory,
- type DashboardTileReference,
- type DashboardTileSnapshotStoreContext,
- type RestoredDashboardTileSnapshot,
-} from "$lib/client/dashboard-refresh";
-import { dashboardDocumentToUiDashboard } from "$lib/ui-adapter/model-renderer";
-import {
- DashboardFrame,
- SystemState,
- ThemeToggle,
- persistUiTheme,
- resolveInitialUiTheme,
- type UiTheme,
- type UiSeverity,
- type UiDashboardPreview,
-} from "@dimensionlab/ui";
-
-type DashboardTileResponse =
- | {
- state: "ready";
- tile: DashboardTileReference;
- item: DashboardModule | ServiceEntry | StatusItem | TelemetryCard;
- }
- | {
- state: "not_found";
- tile: DashboardTileReference;
- message: string;
- }
- | {
- state: "disabled";
- tile: DashboardTileReference;
- message: string;
- };
-
-type DashboardTilesBatchResponse = {
- state: "ready";
- tiles: DashboardTileResponse[];
-};
-
-type DashboardTileHydrationResult = "aborted" | "failed" | "ready";
-
-const dashboardTileHydrationConcurrency = 6;
-const dashboardFallbackRefreshIntervalMs = 30_000;
-const dashboardViewportObservationTimeoutMs = 80;
-type DashboardTileSnapshotStore = ReturnType;
-
-export function AppStateView({
- dashboard,
- onThemeChange,
- theme,
- hydratingItemIds,
-}: {
- dashboard: DashboardRuntimeState;
- hydratingItemIds?: ReadonlySet;
- onThemeChange?: (theme: UiTheme) => void;
- theme?: UiTheme;
-}) {
- const themeToggle =
- theme && onThemeChange ? (
-
- ) : null;
-
- if (dashboard.state === "ready") {
- const uiDashboard = markHydratingItems(
- dashboardDocumentToUiDashboard(dashboard.document),
- hydratingItemIds,
- );
-
- return (
-
- );
- }
-
- const detail = `${dashboard.subtitle}: ${dashboard.message}`;
- const errors = dashboard.state === "invalid" ? dashboard.errors : [];
-
- return (
-
- {themeToggle ? (
- {themeToggle}
- ) : null}
-
- {errors.length ? (
-
- {errors.map((error) => (
- {error}
- ))}
-
- ) : null}
-
- );
-}
-
-export function resolveDocumentMetadata(dashboard: DashboardRuntimeState): {
- description: string;
- title: string;
-} {
- if (dashboard.state === "ready") {
- const uiDashboard = dashboardDocumentToUiDashboard(dashboard.document);
- return {
- title: uiDashboard.title,
- description:
- uiDashboard.subtitle ||
- dashboard.document.metadata.description ||
- uiDashboard.title,
- };
- }
-
- return {
- title: dashboard.title,
- description: dashboard.subtitle || dashboard.message,
- };
-}
-
-export default function App() {
- const [dashboard, setDashboard] =
- useState(null);
- const [hydratingItemIds, setHydratingItemIds] = useState>(
- () => new Set(),
- );
- const [theme, setTheme] = useState(() => {
- if (typeof window === "undefined") return "dark";
-
- return resolveInitialUiTheme(getThemeStorage());
- });
-
- useEffect(() => {
- if (!dashboard) return;
-
- const metadata = resolveDocumentMetadata(dashboard);
- document.title = metadata.title;
-
- let description = document.querySelector(
- 'meta[name="description"]',
- );
- if (!description) {
- description = document.createElement("meta");
- description.name = "description";
- document.head.append(description);
- }
- description.content = metadata.description;
- }, [dashboard]);
-
- useEffect(() => {
- document.documentElement.dataset.uiTheme = theme;
- persistUiTheme(theme, getThemeStorage());
- }, [theme]);
-
- useEffect(() => {
- let cancelled = false;
- let refreshTimer: number | undefined;
- let unsubscribeTileEvents: (() => void) | undefined;
- let lastRefreshIntervalMs = dashboardFallbackRefreshIntervalMs;
- let hydrationRun = 0;
- const requestAborter = createDashboardRequestAborter();
- const performanceMarks = createDashboardPerformanceMarks();
- const refreshDelay = createDashboardRefreshDelay();
- const tileBackoff = createDashboardTileBackoff();
- const tileSnapshotStore = createDashboardTileSnapshotStore(
- getTileSnapshotStorage(),
- );
-
- function refreshPaused() {
- return shouldPauseDashboardRefresh({
- visibilityState: document.visibilityState,
- online: navigator.onLine,
- });
- }
-
- function clearRefreshTimer() {
- if (refreshTimer !== undefined) {
- window.clearTimeout(refreshTimer);
- refreshTimer = undefined;
- }
- }
-
- function scheduleNextRefresh(baseDelayMs: number) {
- clearRefreshTimer();
- if (cancelled || refreshPaused()) return;
-
- refreshTimer = window.setTimeout(() => {
- refreshTimer = undefined;
- void loadDashboard();
- }, refreshDelay.nextDelayMs(baseDelayMs));
- }
-
- function pauseRefreshes() {
- clearRefreshTimer();
- unsubscribeTileEvents?.();
- unsubscribeTileEvents = undefined;
- requestAborter.abortActiveRequests();
- setHydratingItemIds(new Set());
- }
-
- async function loadDashboard() {
- if (cancelled || refreshPaused()) return;
-
- clearRefreshTimer();
- const shellSignal = requestAborter.beginShellRun();
-
- try {
- const response = await fetch("/api/dashboard", { signal: shellSignal });
- const nextDashboard = (await response.json()) as DashboardRuntimeState;
-
- if (cancelled || shellSignal.aborted || refreshPaused()) return;
- refreshDelay.recordSuccess();
- performanceMarks.markShellLoad();
- const restored = restoreDashboardTileSnapshots(
- nextDashboard,
- nextDashboard.state === "ready"
- ? tileSnapshotStore.restore({
- currentRevisionId: nextDashboard.currentRevisionId,
- schemaVersion: nextDashboard.schemaVersion,
- })
- : [],
- );
- setDashboard(restored.dashboard);
-
- const currentRun = ++hydrationRun;
- if (
- restored.dashboard.state === "ready" &&
- restored.dashboard.liveDatasourceHydration?.enabled !== false
- ) {
- const tiles = dashboardHydrationTiles(restored.dashboard.document).filter(
- (tile) => tileBackoff.canAttempt(dashboardTileKey(tile)),
- );
- const tileSignal = requestAborter.beginTileRun();
- setHydratingItemIds(
- new Set(
- tiles
- .map(dashboardTileKey)
- .filter((key) => !restored.restoredItemIds.has(key)),
- ),
- );
- hydrateDashboardTiles(
- tiles,
- currentRun,
- tileSignal,
- {
- currentRevisionId: restored.dashboard.currentRevisionId,
- schemaVersion: restored.dashboard.schemaVersion,
- },
- );
- subscribeDashboardTileEvents(
- currentRun,
- tileSignal,
- {
- currentRevisionId: restored.dashboard.currentRevisionId,
- schemaVersion: restored.dashboard.schemaVersion,
- },
- );
- } else {
- setHydratingItemIds(new Set());
- }
-
- const refreshIntervalSeconds =
- nextDashboard.state === "ready"
- ? nextDashboard.document.metadata.refreshIntervalSeconds
- : undefined;
-
- if (refreshIntervalSeconds && !refreshPaused()) {
- lastRefreshIntervalMs = refreshIntervalSeconds * 1000;
- scheduleNextRefresh(lastRefreshIntervalMs);
- }
- } catch (error) {
- if (!isAbortError(error) && !cancelled) {
- refreshDelay.recordFailure();
- console.error("Dashboard refresh failed", error);
- scheduleNextRefresh(lastRefreshIntervalMs);
- }
- }
- }
-
- function hydrateDashboardTiles(
- tiles: DashboardTileReference[],
- run: number,
- signal: AbortSignal,
- snapshotContext: DashboardTileSnapshotStoreContext,
- ) {
- const modelIds = tiles.map(dashboardTileModelId);
-
- void runViewportAwareDashboardHydrationQueue({
- batchSize: dashboardTileHydrationConcurrency,
- collectVisibleModelIds: async () => {
- await waitForDashboardRenderFrame(signal);
- return collectVisibleDashboardModelIds({
- clearTimeout: window.clearTimeout.bind(window),
- createObserver: getDashboardIntersectionObserverFactory(),
- documentTarget: document,
- modelIds,
- setTimeout: window.setTimeout.bind(window),
- signal,
- timeoutMs: dashboardViewportObservationTimeoutMs,
- });
- },
- concurrency: dashboardTileHydrationConcurrency,
- getModelId: dashboardTileModelId,
- hydrate: async (tile) => {
- const key = dashboardTileKey(tile);
- const result = await hydrateDashboardTile(
- tile,
- run,
- signal,
- snapshotContext,
- );
-
- if (result === "ready") {
- tileBackoff.recordSuccess(key);
- } else if (result === "failed") {
- tileBackoff.recordFailure(key);
- }
- },
- hydrateBatch: async (batch) => {
- const results = await hydrateDashboardTileBatch(
- batch,
- run,
- signal,
- snapshotContext,
- );
-
- for (const { result, tile } of results) {
- const key = dashboardTileKey(tile);
- if (result === "ready") {
- tileBackoff.recordSuccess(key);
- } else if (result === "failed") {
- tileBackoff.recordFailure(key);
- }
- }
- },
- items: tiles,
- onAllItemsSettled: () => performanceMarks.markAllTilesSettled(),
- onVisibleItemsSettled: () => performanceMarks.markVisibleTilesReady(),
- signal,
- waitForIdle: () =>
- waitForDashboardHydrationIdle({
- ...getDashboardIdleCallbacks(),
- clearTimeout: window.clearTimeout.bind(window),
- setTimeout: window.setTimeout.bind(window),
- signal,
- }),
- });
- }
-
- function subscribeDashboardTileEvents(
- run: number,
- signal: AbortSignal,
- snapshotContext: DashboardTileSnapshotStoreContext,
- ) {
- unsubscribeTileEvents?.();
- unsubscribeTileEvents = subscribeToDashboardTileEvents({
- onTile: (event) => {
- const tileResponse = event as DashboardTileResponse;
- if (!isDashboardTileResponse(tileResponse)) return;
- applyDashboardTileHydrationResponse(
- tileResponse.tile,
- tileResponse,
- run,
- signal,
- snapshotContext,
- );
- },
- onUnavailable: () => {
- unsubscribeTileEvents = undefined;
- },
- });
- }
-
- async function hydrateDashboardTileBatch(
- tiles: DashboardTileReference[],
- run: number,
- signal: AbortSignal,
- snapshotContext: DashboardTileSnapshotStoreContext,
- ): Promise> {
- 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 {
- const key = dashboardTileKey(tile);
-
- try {
- const response = await fetch(dashboardTileUrl(tile), { signal });
- const tileResponse = (await response.json()) as DashboardTileResponse;
-
- if (!response.ok) {
- return "failed";
- }
-
- const result = applyDashboardTileHydrationResponse(
- tile,
- tileResponse,
- run,
- signal,
- snapshotContext,
- );
- if (result !== "ready") {
- return signal.aborted || cancelled || run !== hydrationRun
- ? "aborted"
- : "failed";
- }
-
- return "ready";
- } catch (error) {
- if (!isAbortError(error) && !cancelled) {
- console.error("Dashboard tile hydration failed", error);
- return "failed";
- }
- return "aborted";
- } finally {
- finishDashboardTileHydration(key, run, signal);
- }
- }
-
- function applyDashboardTileHydrationResponse(
- tile: DashboardTileReference,
- tileResponse: DashboardTileResponse | undefined,
- run: number,
- signal: AbortSignal,
- snapshotContext: DashboardTileSnapshotStoreContext,
- ): DashboardTileHydrationResult {
- if (
- cancelled ||
- signal.aborted ||
- run !== hydrationRun ||
- !tileResponse ||
- dashboardTileMatchKey(tileResponse.tile) !== dashboardTileMatchKey(tile) ||
- tileResponse.state !== "ready"
- ) {
- return signal.aborted || cancelled || run !== hydrationRun
- ? "aborted"
- : "failed";
- }
-
- const readyTileResponse = tileResponse;
- setDashboard((current) =>
- current?.state === "ready"
- ? {
- ...current,
- document: applyDashboardTile(current.document, readyTileResponse),
- }
- : current,
- );
- tileSnapshotStore.saveReadyTile({
- ...snapshotContext,
- response: readyTileResponse,
- });
- performanceMarks.markFirstTileReady();
- return "ready";
- }
-
- function finishDashboardTileHydration(
- key: string,
- run: number,
- signal: AbortSignal,
- ) {
- if (!cancelled && !signal.aborted && run === hydrationRun) {
- setHydratingItemIds((current) => {
- const next = new Set(current);
- next.delete(key);
- return next;
- });
- }
- }
-
- const detachRefreshLifecycle = attachDashboardRefreshLifecycle({
- documentTarget: document,
- windowTarget: window,
- loadDashboard,
- pauseRefreshes,
- refreshPaused: () => cancelled || refreshPaused(),
- });
-
- void loadDashboard();
-
- return () => {
- cancelled = true;
- clearRefreshTimer();
- unsubscribeTileEvents?.();
- requestAborter.abortActiveRequests();
- detachRefreshLifecycle();
- };
- }, []);
-
- return dashboard ? (
-
- ) : null;
-}
-
-function stateSeverity(state: DashboardRuntimeState["state"]): UiSeverity {
- if (state === "invalid") return "danger";
- if (state === "loading") return "loading";
- return "stale";
-}
-
-function stateIcon(state: DashboardRuntimeState["state"]): string {
- if (state === "invalid") return "mdi:file-alert-outline";
- if (state === "loading") return "mdi:progress-clock";
- return "mdi:tray";
-}
-
-function getThemeStorage(): Storage | undefined {
- try {
- return window.localStorage;
- } catch {
- return undefined;
- }
-}
-
-function getTileSnapshotStorage(): Storage | undefined {
- try {
- return window.localStorage;
- } catch {
- return undefined;
- }
-}
-
-type DashboardIdleWindow = Window & {
- cancelIdleCallback?: (handle: number) => void;
- requestIdleCallback?: (
- callback: () => void,
- options?: { timeout?: number },
- ) => number;
-};
-
-function getDashboardIdleCallbacks() {
- const idleWindow = window as DashboardIdleWindow;
- return {
- cancelIdleCallback: idleWindow.cancelIdleCallback?.bind(idleWindow),
- requestIdleCallback: idleWindow.requestIdleCallback?.bind(idleWindow),
- };
-}
-
-function getDashboardIntersectionObserverFactory():
- | DashboardIntersectionObserverFactory
- | undefined {
- if (typeof window.IntersectionObserver === "undefined") return undefined;
-
- return (callback) =>
- new window.IntersectionObserver((entries) => {
- callback(entries);
- });
-}
-
-async function waitForDashboardRenderFrame(signal: AbortSignal): Promise {
- if (signal.aborted) return;
-
- await new Promise((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;
-} {
- 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,
- ),
- },
- restoredItemIds: new Set([
- ...current.restoredItemIds,
- dashboardTileKey(snapshot.response.tile),
- ]),
- }),
- {
- dashboard,
- restoredItemIds: new Set(),
- },
- );
-}
-
-function markHydratingItems(
- dashboard: UiDashboardPreview,
- hydratingItemIds?: ReadonlySet,
-): 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,
-): 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,
- ),
- };
-}
diff --git a/apps/web/src/app.css b/apps/web/src/app.css
deleted file mode 100644
index eeaabcb..0000000
--- a/apps/web/src/app.css
+++ /dev/null
@@ -1,170 +0,0 @@
-@import "tailwindcss";
-@import "@dimensionlab/ui/styles.css";
-@import "tw-animate-css";
-@import "shadcn/tailwind.css";
-
-@custom-variant dark (&:is(.dark *));
-
-@theme inline {
- --font-heading: var(--font-sans);
- --font-sans: 'Geist Variable', sans-serif;
- --color-sidebar-ring: var(--sidebar-ring);
- --color-sidebar-border: var(--sidebar-border);
- --color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
- --color-sidebar-accent: var(--sidebar-accent);
- --color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
- --color-sidebar-primary: var(--sidebar-primary);
- --color-sidebar-foreground: var(--sidebar-foreground);
- --color-sidebar: var(--sidebar);
- --color-chart-5: var(--chart-5);
- --color-chart-4: var(--chart-4);
- --color-chart-3: var(--chart-3);
- --color-chart-2: var(--chart-2);
- --color-chart-1: var(--chart-1);
- --color-ring: var(--ring);
- --color-input: var(--input);
- --color-border: var(--border);
- --color-destructive: var(--destructive);
- --color-accent-foreground: var(--accent-foreground);
- --color-accent: var(--accent);
- --color-muted-foreground: var(--muted-foreground);
- --color-muted: var(--muted);
- --color-secondary-foreground: var(--secondary-foreground);
- --color-secondary: var(--secondary);
- --color-primary-foreground: var(--primary-foreground);
- --color-primary: var(--primary);
- --color-popover-foreground: var(--popover-foreground);
- --color-popover: var(--popover);
- --color-card-foreground: var(--card-foreground);
- --color-card: var(--card);
- --color-foreground: var(--foreground);
- --color-background: var(--background);
- --radius-sm: calc(var(--radius) * 0.6);
- --radius-md: calc(var(--radius) * 0.8);
- --radius-lg: var(--radius);
- --radius-xl: calc(var(--radius) * 1.4);
- --radius-2xl: calc(var(--radius) * 1.8);
- --radius-3xl: calc(var(--radius) * 2.2);
- --radius-4xl: calc(var(--radius) * 2.6);
-}
-
-:root,
-.dark {
- --background: var(--ui-color-canvas);
- --foreground: var(--ui-color-text);
- --card: var(--ui-color-surface);
- --card-foreground: var(--ui-color-text);
- --popover: var(--ui-color-surface-raised);
- --popover-foreground: var(--ui-color-text);
- --primary: var(--ui-color-accent);
- --primary-foreground: var(--ui-color-canvas);
- --secondary: var(--ui-color-surface-raised);
- --secondary-foreground: var(--ui-color-text);
- --muted: var(--ui-color-surface-raised);
- --muted-foreground: var(--ui-color-muted);
- --accent: var(--ui-color-accent);
- --accent-foreground: var(--ui-color-canvas);
- --destructive: var(--ui-color-danger);
- --border: var(--ui-color-border);
- --input: var(--ui-color-border-strong);
- --ring: var(--ui-color-accent);
- --chart-1: var(--ui-color-accent);
- --chart-2: var(--ui-color-ok);
- --chart-3: var(--ui-color-warning);
- --chart-4: var(--ui-color-danger);
- --chart-5: var(--ui-color-stale);
- --radius: 0.5rem;
- --sidebar: var(--ui-color-surface);
- --sidebar-foreground: var(--ui-color-text);
- --sidebar-primary: var(--ui-color-accent);
- --sidebar-primary-foreground: var(--ui-color-canvas);
- --sidebar-accent: var(--ui-color-surface-raised);
- --sidebar-accent-foreground: var(--ui-color-text);
- --sidebar-border: rgba(244, 244, 244, 0.16);
- --sidebar-ring: var(--ui-color-accent);
-}
-
-@layer base {
- * {
- @apply border-border outline-ring/50;
- }
- body {
- @apply bg-background text-foreground;
- }
- html {
- @apply font-sans;
- }
-}
-
-html {
- background: var(--ui-color-canvas);
-}
-
-body {
- min-width: 320px;
- min-height: 100vh;
- margin: 0;
- background:
- radial-gradient(circle at 50% 12%, transparent 0 42%, var(--ui-color-backdrop-vignette) 100%),
- linear-gradient(var(--ui-color-grid-line) 1px, transparent 1px),
- linear-gradient(90deg, var(--ui-color-grid-line) 1px, transparent 1px),
- var(--ui-color-canvas);
- background-size: auto, 40px 40px, 40px 40px, auto;
- color: var(--ui-color-text);
- font-family: var(--ui-font-mono);
- text-rendering: geometricPrecision;
-}
-
-button,
-input,
-textarea,
-select {
- font: inherit;
-}
-
-button:focus-visible,
-a:focus-visible,
-[tabindex]:focus-visible {
- outline: 0;
- box-shadow: var(--ui-focus-ring);
-}
-
-a {
- color: inherit;
-}
-
-.state-shell {
- display: grid;
- min-height: 100vh;
- align-content: center;
- gap: var(--ui-space-3);
- padding: var(--ui-space-4);
-}
-
-.state-shell__actions {
- justify-self: center;
-}
-
-.state-shell ul {
- display: grid;
- max-width: 56rem;
- gap: var(--ui-space-2);
- margin: 0;
- border: var(--ui-border);
- background: var(--ui-color-surface-module);
- color: var(--ui-color-muted);
- font-size: 0.76rem;
- list-style-position: inside;
- padding: var(--ui-space-3);
-}
-
-@media (prefers-reduced-motion: reduce) {
- *,
- *::before,
- *::after {
- animation-duration: 0.01ms !important;
- animation-iteration-count: 1 !important;
- scroll-behavior: auto !important;
- transition-duration: 0.01ms !important;
- }
-}
diff --git a/apps/web/src/lib/client/dashboard-refresh.test.ts b/apps/web/src/lib/client/dashboard-refresh.test.ts
deleted file mode 100644
index 77ea795..0000000
--- a/apps/web/src/lib/client/dashboard-refresh.test.ts
+++ /dev/null
@@ -1,633 +0,0 @@
-import { describe, expect, test } from "vitest";
-import {
- attachDashboardRefreshLifecycle,
- collectVisibleDashboardModelIds,
- createDashboardPerformanceMarks,
- createDashboardRefreshDelay,
- dashboardPerformanceMarks,
- createDashboardTileSnapshotStore,
- createDashboardTileBackoff,
- createDashboardRequestAborter,
- runDashboardHydrationQueue,
- runDashboardHydrationBatchQueue,
- runViewportAwareDashboardHydrationQueue,
- shouldPauseDashboardRefresh,
- splitDashboardHydrationItemsByVisibility,
- subscribeToDashboardTileEvents,
- waitForDashboardHydrationIdle,
- type DashboardIntersectionEntry,
-} from "./dashboard-refresh";
-
-describe("dashboard refresh lifecycle", () => {
- test("pauses refreshes when the document is hidden or the browser is offline", () => {
- expect(
- shouldPauseDashboardRefresh({ visibilityState: "visible", online: true }),
- ).toBe(false);
- expect(
- shouldPauseDashboardRefresh({ visibilityState: "hidden", online: true }),
- ).toBe(true);
- expect(
- shouldPauseDashboardRefresh({ visibilityState: "visible", online: false }),
- ).toBe(true);
- });
-
- test("aborts shell and tile requests when a new shell run starts", () => {
- const aborter = createDashboardRequestAborter();
- const shellSignal = aborter.beginShellRun();
- const tileSignal = aborter.beginTileRun();
-
- const nextShellSignal = aborter.beginShellRun();
-
- expect(shellSignal.aborted).toBe(true);
- expect(tileSignal.aborted).toBe(true);
- expect(nextShellSignal.aborted).toBe(false);
- });
-
- test("aborts active requests when refreshes are paused", () => {
- const aborter = createDashboardRequestAborter();
- const shellSignal = aborter.beginShellRun();
- const tileSignal = aborter.beginTileRun();
-
- aborter.abortActiveRequests();
-
- expect(shellSignal.aborted).toBe(true);
- expect(tileSignal.aborted).toBe(true);
- });
-
- test("pauses on hidden, offline, and pagehide events then resumes immediately when visible", () => {
- const documentTarget = new EventTarget();
- const windowTarget = new EventTarget();
- const calls: string[] = [];
- let paused = true;
-
- const detach = attachDashboardRefreshLifecycle({
- documentTarget,
- windowTarget,
- loadDashboard: () => {
- calls.push("load");
- },
- pauseRefreshes: () => {
- calls.push("pause");
- },
- refreshPaused: () => paused,
- });
-
- documentTarget.dispatchEvent(new Event("visibilitychange"));
- windowTarget.dispatchEvent(new Event("offline"));
- windowTarget.dispatchEvent(new Event("pagehide"));
-
- paused = false;
- documentTarget.dispatchEvent(new Event("visibilitychange"));
- windowTarget.dispatchEvent(new Event("online"));
-
- detach();
- documentTarget.dispatchEvent(new Event("visibilitychange"));
- windowTarget.dispatchEvent(new Event("online"));
-
- expect(calls).toEqual(["pause", "pause", "pause", "load", "load"]);
- });
-
- test("limits tile hydration concurrency", async () => {
- let active = 0;
- let maxActive = 0;
- const started: number[] = [];
- const releases = new Map 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((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((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;
- },
- 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) => 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);
- 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();
- 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;
- },
- };
-}
diff --git a/apps/web/src/lib/client/dashboard-refresh.ts b/apps/web/src/lib/client/dashboard-refresh.ts
deleted file mode 100644
index 7c50359..0000000
--- a/apps/web/src/lib/client/dashboard-refresh.ts
+++ /dev/null
@@ -1,886 +0,0 @@
-export interface DashboardRefreshPauseState {
- visibilityState: DocumentVisibilityState;
- online: boolean;
-}
-
-export type DashboardTileReference =
- | { kind: "telemetry"; id: string }
- | { kind: "service"; groupId: string; id: string }
- | { kind: "module"; id: string }
- | { kind: "status"; stripId: string; id: string };
-
-type SnapshotMetricValue = Record;
-
-export type DashboardTileSnapshotItem = {
- detail?: string;
- id: string;
- label?: string;
- severity?: string;
- value?: SnapshotMetricValue | string;
-} & Record;
-
-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;
- 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 {
- concurrency: number;
- hydrate: (item: TItem) => Promise | void;
- items: TItem[];
- signal: AbortSignal;
-}
-
-export async function runDashboardHydrationQueue(
- options: DashboardHydrationQueueOptions,
-): Promise {
- 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 {
- batchSize?: number;
- collectVisibleModelIds: () => Promise>;
- concurrency: number;
- getModelId: (item: TItem) => string;
- hydrate: (item: TItem) => Promise | void;
- hydrateBatch?: (items: TItem[]) => Promise | void;
- items: TItem[];
- onAllItemsSettled?: () => void;
- onVisibleItemsSettled?: () => void;
- signal: AbortSignal;
- waitForIdle: () => Promise;
-}
-
-export async function runViewportAwareDashboardHydrationQueue(
- options: DashboardViewportHydrationQueueOptions,
-): Promise {
- 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(
- options: DashboardViewportHydrationQueueOptions,
- items: TItem[],
-): Promise {
- 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(options: {
- batchSize: number;
- hydrateBatch: (items: TItem[]) => Promise | void;
- items: TItem[];
- signal: AbortSignal;
-}): Promise {
- 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(options: {
- getModelId: (item: TItem) => string;
- items: TItem[];
- visibleModelIds: ReadonlySet;
-}): {
- 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;
-}
-
-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;
-
-export interface DashboardVisibleModelIdCollectorOptions {
- clearTimeout?: (handle: DashboardTimerHandle) => void;
- createObserver?: DashboardIntersectionObserverFactory;
- documentTarget: DashboardViewportElementSource;
- modelIds: Iterable;
- setTimeout?: (callback: () => void, timeoutMs: number) => DashboardTimerHandle;
- signal: AbortSignal;
- timeoutMs?: number;
-}
-
-export async function collectVisibleDashboardModelIds(
- options: DashboardVisibleModelIdCollectorOptions,
-): Promise> {
- 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();
- 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 {
- 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((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) => 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();
-
- 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;
-}
-
-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;
- 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;
- },
- ): 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 {
- 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;
-}
diff --git a/apps/web/src/lib/presentation-boundary.test.ts b/apps/web/src/lib/presentation-boundary.test.ts
deleted file mode 100644
index 8a62205..0000000
--- a/apps/web/src/lib/presentation-boundary.test.ts
+++ /dev/null
@@ -1,80 +0,0 @@
-import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
-import { join } from "node:path";
-import { describe, expect, test } from "vitest";
-
-const appRoot = process.cwd().endsWith(`${join("apps", "web")}`)
- ? process.cwd()
- : join(process.cwd(), "apps", "web");
-const repoRoot = existsSync(join(process.cwd(), "turbo.json"))
- ? process.cwd()
- : join(appRoot, "..", "..");
-const presentationRoots = [
- join(repoRoot, "packages", "ui", "src"),
- join(appRoot, "src", "App.tsx"),
- join(appRoot, "src", "app.css"),
- join(appRoot, "src", "lib", "ui-adapter"),
-];
-
-const forbiddenTerms = [
- "dimensionlab",
- "dimension lab",
- "vaultwarden",
- "forgejo",
- "grafana",
- "uptime kuma",
- "prometheus",
- "backrest",
- "open webui",
- "comfyui",
- "adminer",
- "cockpit",
- "ollama",
- "dimensionlab.net",
-];
-
-describe("presentation content boundary", () => {
- test("keeps environment-specific content out of route and UI implementation", () => {
- const source = withoutInternalPackageScope(
- presentationRoots.map(readPresentationSource).join("\n").toLowerCase(),
- );
-
- expect(forbiddenTerms.filter((term) => source.includes(term))).toEqual([]);
- });
-
- test("does not keep legacy presentation component files in the React runtime", () => {
- const legacyExtension = [".sve", "lte"].join("");
-
- expect(findFiles(join(appRoot, "src"), legacyExtension)).toEqual([]);
- });
-});
-
-function readPresentationSource(path: string): string {
- if (!existsSync(path)) return "";
-
- const stats = statSync(path);
- if (stats.isFile()) {
- if (path.endsWith(".test.ts")) return "";
- if (path.includes(`${join("packages", "ui", "src", "stories")}${"/"}`)) {
- return "";
- }
- if (!/\.(tsx|ts|js|mjs|css|json)$/.test(path)) return "";
- return readFileSync(path, "utf8");
- }
-
- return readdirSync(path)
- .map((entry) => readPresentationSource(join(path, entry)))
- .join("\n");
-}
-
-function findFiles(path: string, extension: string): string[] {
- const stats = statSync(path);
- if (stats.isFile()) return path.endsWith(extension) ? [path] : [];
-
- return readdirSync(path).flatMap((entry) => findFiles(join(path, entry), extension));
-}
-
-function withoutInternalPackageScope(source: string): string {
- return source
- .replaceAll("@dimensionlab/dashboard-model", "@internal/dashboard-model")
- .replaceAll("@dimensionlab/ui", "@internal/ui");
-}
diff --git a/apps/web/src/lib/server/agent-config/agent-config.test.ts b/apps/web/src/lib/server/agent-config/agent-config.test.ts
deleted file mode 100644
index cdc60cf..0000000
--- a/apps/web/src/lib/server/agent-config/agent-config.test.ts
+++ /dev/null
@@ -1,595 +0,0 @@
-import { existsSync, rmSync } from "node:fs";
-import { mkdtemp } from "node:fs/promises";
-import { tmpdir } from "node:os";
-import { join } from "node:path";
-import { afterEach, describe, expect, test } from "vitest";
-import type { DashboardDocument } from "@dimensionlab/dashboard-model";
-import { genericDashboardFixture } from "@dimensionlab/dashboard-model/fixtures";
-import { createDashboardStore, type DashboardStore } from "$lib/server/db/dashboard-store";
-import {
- AgentConfigAuthorizationError,
- authorizeAgentConfigRequest,
- handleAgentDashboardRequest,
- previewDashboardChanges,
- publishDashboardChanges,
- rollbackDashboardRevision,
- type AgentDashboardOperation,
- type JsonPatchOperation,
-} from ".";
-
-const stores: DashboardStore[] = [];
-const tempRoots: string[] = [];
-
-afterEach(() => {
- stores.splice(0).forEach((store) => store.close());
- tempRoots.splice(0).forEach((path) => rmSync(path, { force: true, recursive: true }));
-});
-
-describe("agent dashboard configuration API", () => {
- test("previews typed dashboard operations with an RFC 6902-compatible patch", () => {
- const operations = exampleOperations();
-
- const result = previewDashboardChanges(genericDashboardFixture, operations);
-
- expect(result.ok).toBe(true);
- if (!result.ok) throw new Error("expected preview success");
- expect(result.document.serviceGroups[0]?.id).toBe("edge");
- expect(result.document.serviceGroups[0]?.services[0]).toMatchObject({
- id: "edge-router",
- datasource: {
- type: "external",
- adapter: "http-status",
- reference: "GET https://edge.example.test/api/status",
- },
- });
- expect(result.document.layout.telemetry).toEqual([
- "service-uptime",
- "edge-latency",
- "queue-depth",
- ]);
- expect(genericDashboardFixture.serviceGroups.map((group) => group.id)).toEqual([
- "core-services",
- ]);
- expect(result.patch.length).toBeGreaterThan(0);
- expect(result.patch.every((operation) => operation.path.startsWith("/"))).toBe(true);
- expect(result.patch.map((operation) => operation.op)).toContain("add");
- });
-
- test("returns structured repairable errors for invalid operations", () => {
- const result = previewDashboardChanges(genericDashboardFixture, [
- {
- type: "add_service",
- groupId: "missing-group",
- service: exampleService(),
- },
- ]);
-
- expect(result.ok).toBe(false);
- if (result.ok) throw new Error("expected preview failure");
- expect(result.errors).toEqual([
- expect.objectContaining({
- code: "group_not_found",
- operationIndex: 0,
- path: "/serviceGroups",
- }),
- ]);
- });
-
- test("rejects unsupported target-specific mutations", () => {
- const datasourceResult = previewDashboardChanges(genericDashboardFixture, [
- {
- type: "connect_datasource",
- target: { kind: "statusItem", stripId: "runtime", id: "status" },
- datasource: {
- type: "external",
- adapter: "http-status",
- reference: "GET https://status.example.test/api",
- },
- },
- ]);
- expect(datasourceResult.ok).toBe(false);
- if (datasourceResult.ok) throw new Error("expected datasource failure");
- expect(datasourceResult.errors[0]).toMatchObject({
- code: "unsupported_target",
- operationIndex: 0,
- path: "/statusStrips",
- });
-
- const thresholdResult = previewDashboardChanges(genericDashboardFixture, [
- {
- type: "set_status_rule",
- target: { kind: "service", id: "identity" },
- thresholds: { warning: 1 },
- },
- ]);
- expect(thresholdResult.ok).toBe(false);
- if (thresholdResult.ok) throw new Error("expected threshold failure");
- expect(thresholdResult.errors[0]).toMatchObject({
- code: "unsupported_target",
- operationIndex: 0,
- path: "/serviceGroups",
- });
- });
-
- test("removes status strip items through the shared remove operation", () => {
- const result = previewDashboardChanges(genericDashboardFixture, [
- {
- type: "remove_item",
- target: { kind: "statusItem", stripId: "runtime", id: "sync" },
- },
- ]);
-
- expect(result.ok).toBe(true);
- if (!result.ok) throw new Error("expected remove success");
- expect(result.document.statusStrips[0]?.items.map((item) => item.id)).toEqual([
- "status",
- ]);
- expect(result.patch.map((operation) => operation.op)).toContain("remove");
- });
-
- test("returns RFC 6902-applicable patches for multiple array removals", () => {
- const result = previewDashboardChanges(genericDashboardFixture, [
- {
- type: "remove_item",
- target: { kind: "statusItem", stripId: "runtime", id: "status" },
- },
- {
- type: "remove_item",
- target: { kind: "statusItem", stripId: "runtime", id: "sync" },
- },
- ]);
-
- expect(result.ok).toBe(true);
- if (!result.ok) throw new Error("expected remove success");
- expect(
- result.patch
- .filter((operation) => operation.op === "remove")
- .map((operation) => operation.path),
- ).toEqual(["/statusStrips/0/items/1", "/statusStrips/0/items/0"]);
- expect(applyJsonPatch(genericDashboardFixture, result.patch)).toMatchObject({
- statusStrips: [{ items: [] }],
- });
- });
-
- test("rejects ambiguous service and status targets", () => {
- const duplicateDocument = documentWithDuplicateNestedIds();
-
- const connect = previewDashboardChanges(duplicateDocument, [
- {
- type: "connect_datasource",
- target: { kind: "service", id: "identity" },
- datasource: {
- type: "external",
- adapter: "http-status",
- reference: "GET https://identity.example.test/health",
- },
- },
- ]);
- expect(connect.ok).toBe(false);
- if (connect.ok) throw new Error("expected ambiguous service failure");
- expect(connect.errors[0]).toMatchObject({
- code: "ambiguous_target",
- operationIndex: 0,
- path: "/serviceGroups",
- });
-
- const remove = previewDashboardChanges(duplicateDocument, [
- {
- type: "remove_item",
- target: { kind: "service", id: "identity" },
- },
- ]);
- expect(remove.ok).toBe(false);
- if (remove.ok) throw new Error("expected ambiguous removal failure");
- expect(remove.errors[0]).toMatchObject({
- code: "ambiguous_target",
- operationIndex: 0,
- path: "/serviceGroups",
- });
-
- const status = previewDashboardChanges(duplicateDocument, [
- {
- type: "set_status_rule",
- target: { kind: "statusItem", id: "status" },
- value: "Healthy",
- },
- ]);
- expect(status.ok).toBe(false);
- if (status.ok) throw new Error("expected ambiguous status failure");
- expect(status.errors[0]).toMatchObject({
- code: "ambiguous_target",
- operationIndex: 0,
- path: "/statusStrips",
- });
- });
-
- test("rejects create_dashboard when it is not the first operation", () => {
- const result = previewDashboardChanges(genericDashboardFixture, [
- {
- type: "remove_item",
- target: { kind: "telemetry", id: "queue-depth" },
- },
- {
- type: "create_dashboard",
- document: genericDashboardFixture,
- },
- ]);
-
- expect(result.ok).toBe(false);
- if (result.ok) throw new Error("expected sequence failure");
- expect(result.errors[0]).toMatchObject({
- code: "invalid_operation_sequence",
- operationIndex: 1,
- path: "/1",
- });
- });
-
- test("publishes valid operations as a persisted dashboard revision", async () => {
- const { dbPath, store } = await createTestStore();
- const seed = store.seedDashboardIfEmpty(genericDashboardFixture, {
- actor: "seed",
- message: "initial dashboard",
- });
-
- const result = publishDashboardChanges(store, exampleOperations(), {
- actor: "agent",
- message: "add edge router",
- });
-
- expect(result.ok).toBe(true);
- if (!result.ok) throw new Error("expected publish success");
- expect(existsSync(dbPath)).toBe(true);
- expect(result.revision.operation).toBe("commit");
- expect(result.revision.actor).toBe("agent");
- expect(result.revision.message).toBe("add edge router");
- expect(result.previousRevisionId).toBe(seed.id);
- expect(result.patch.length).toBeGreaterThan(0);
- expect(store.getActiveDashboard()?.document.serviceGroups[0]?.id).toBe("edge");
- expect(store.listRevisions()).toHaveLength(2);
- });
-
- test("does not publish invalid operations", async () => {
- const { store } = await createTestStore();
- const seed = store.seedDashboardIfEmpty(genericDashboardFixture, {
- actor: "seed",
- });
-
- const result = publishDashboardChanges(store, [
- {
- type: "remove_item",
- target: { kind: "telemetry", id: "missing-metric" },
- },
- ]);
-
- expect(result.ok).toBe(false);
- if (result.ok) throw new Error("expected publish failure");
- expect(result.errors[0]).toMatchObject({
- code: "item_not_found",
- operationIndex: 0,
- });
- expect(store.getActiveDashboard()?.currentRevisionId).toBe(seed.id);
- expect(store.listRevisions()).toHaveLength(1);
- });
-
- test("rolls back through the agent-safe revision path", async () => {
- const { store } = await createTestStore();
- const seed = store.seedDashboardIfEmpty(genericDashboardFixture, {
- actor: "seed",
- });
- const publish = publishDashboardChanges(store, exampleOperations(), {
- actor: "agent",
- });
- expect(publish.ok).toBe(true);
-
- const rollback = rollbackDashboardRevision(store, seed.id, {
- actor: "agent",
- message: "restore previous dashboard",
- });
-
- expect(rollback.operation).toBe("rollback");
- expect(rollback.actor).toBe("agent");
- expect(rollback.sourceRevisionId).toBe(seed.id);
- expect(store.getActiveDashboard()?.document.metadata.title).toBe("Operations Console");
- expect(store.listRevisions().map((revision) => revision.operation)).toEqual([
- "rollback",
- "commit",
- "seed",
- ]);
- });
-
- test("authenticates agent configuration requests with a shared token", () => {
- const authorized = new Request("https://dimensionlab.test/api/agent/dashboard", {
- headers: { authorization: "Bearer shared-secret" },
- });
- const rejected = new Request("https://dimensionlab.test/api/agent/dashboard");
-
- expect(authorizeAgentConfigRequest(authorized, "shared-secret")).toEqual({
- ok: true,
- });
- expect(() => authorizeAgentConfigRequest(rejected, "shared-secret")).toThrow(
- AgentConfigAuthorizationError,
- );
- expect(() => authorizeAgentConfigRequest(authorized, "")).toThrow(
- AgentConfigAuthorizationError,
- );
- });
-
- test("handles preview, publish, and rollback requests through the HTTP adapter", async () => {
- const { store } = await createTestStore();
- const seed = store.seedDashboardIfEmpty(genericDashboardFixture, {
- actor: "seed",
- });
-
- const preview = await handleAgentDashboardRequest(
- jsonRequest({
- action: "preview_changes",
- operations: exampleOperations(),
- }),
- { store, token: "shared-secret" },
- );
- expect(preview.status).toBe(200);
- expect(await preview.json()).toMatchObject({
- ok: true,
- action: "preview_changes",
- patch: expect.any(Array),
- });
- expect(store.listRevisions()).toHaveLength(1);
-
- const publish = await handleAgentDashboardRequest(
- jsonRequest({
- action: "publish_changes",
- actor: "agent",
- message: "publish edge router",
- operations: exampleOperations(),
- }),
- { store, token: "shared-secret" },
- );
- expect(publish.status).toBe(200);
- expect(await publish.json()).toMatchObject({
- ok: true,
- action: "publish_changes",
- revision: { operation: "commit", actor: "agent" },
- });
- expect(store.listRevisions()).toHaveLength(2);
-
- const rollback = await handleAgentDashboardRequest(
- jsonRequest({
- action: "rollback_revision",
- actor: "agent",
- revisionId: seed.id,
- }),
- { store, token: "shared-secret" },
- );
- expect(rollback.status).toBe(200);
- expect(await rollback.json()).toMatchObject({
- ok: true,
- action: "rollback_revision",
- revision: { operation: "rollback", sourceRevisionId: seed.id },
- });
- });
-
- test("returns a structured error for unknown rollback revisions", async () => {
- const { store } = await createTestStore();
- store.seedDashboardIfEmpty(genericDashboardFixture, {
- actor: "seed",
- });
-
- const rollback = await handleAgentDashboardRequest(
- jsonRequest({
- action: "rollback_revision",
- actor: "agent",
- revisionId: "missing-revision",
- }),
- { store, token: "shared-secret" },
- );
-
- expect(rollback.status).toBe(404);
- expect(await rollback.json()).toMatchObject({
- ok: false,
- errors: [
- {
- code: "revision_not_found",
- path: "/revisionId",
- },
- ],
- });
- });
-
- test("previews create_dashboard requests against an empty store", async () => {
- const { store } = await createTestStore();
-
- const preview = await handleAgentDashboardRequest(
- jsonRequest({
- action: "preview_changes",
- operations: [
- {
- type: "create_dashboard",
- document: genericDashboardFixture,
- },
- ],
- }),
- { store, token: "shared-secret" },
- );
-
- expect(preview.status).toBe(200);
- expect(await preview.json()).toMatchObject({
- ok: true,
- action: "preview_changes",
- document: {
- metadata: {
- title: "Operations Console",
- },
- },
- });
- expect(store.listRevisions()).toHaveLength(0);
- });
-});
-
-async function createTestStore() {
- const root = await mkdtemp(join(tmpdir(), "dimensionlab-agent-config-"));
- tempRoots.push(root);
- const dbPath = join(root, "dashboard.sqlite");
- const store = createDashboardStore({
- databaseUrl: `file:${dbPath}`,
- });
- stores.push(store);
-
- return { dbPath, store };
-}
-
-function exampleOperations(): AgentDashboardOperation[] {
- return [
- {
- type: "add_section",
- section: {
- id: "edge",
- title: "Edge",
- layout: "list",
- },
- },
- {
- type: "add_service",
- groupId: "edge",
- service: exampleService(),
- },
- {
- type: "add_metric_card",
- card: {
- id: "edge-latency",
- label: "Edge Latency",
- value: { kind: "latency", value: 12, precision: 0 },
- severity: "ok",
- detail: "p95",
- datasource: {
- type: "external",
- adapter: "prometheus",
- reference: "histogram_quantile(0.95, edge_request_duration_seconds_bucket)",
- },
- },
- position: { afterId: "service-uptime" },
- },
- {
- type: "connect_datasource",
- target: {
- kind: "service",
- groupId: "edge",
- id: "edge-router",
- },
- datasource: {
- type: "external",
- adapter: "http-status",
- reference: "GET https://edge.example.test/api/status",
- },
- },
- {
- type: "set_status_rule",
- target: { kind: "telemetry", id: "edge-latency" },
- thresholds: { warning: 50, danger: 100 },
- },
- {
- type: "arrange_item",
- area: "serviceGroups",
- id: "edge",
- index: 0,
- },
- ];
-}
-
-function exampleService() {
- return {
- id: "edge-router",
- label: "Edge Router",
- description: "Ingress and routing",
- icon: "mdi:router-network",
- severity: "ok" as const,
- detail: "pending datasource",
- datasource: { type: "placeholder" as const, reason: "health adapter pending" },
- link: {
- href: "https://edge.example.test",
- label: "Open Edge Router",
- external: true,
- },
- };
-}
-
-function documentWithDuplicateNestedIds(): DashboardDocument {
- const document = structuredClone(genericDashboardFixture);
- document.layout.serviceGroups.push("secondary-services");
- document.serviceGroups.push({
- id: "secondary-services",
- title: "Secondary Services",
- layout: "list",
- services: [
- {
- ...document.serviceGroups[0].services[0],
- label: "Shadow Identity",
- },
- ],
- });
- document.layout.statusStrips.push("secondary-runtime");
- document.statusStrips.push({
- id: "secondary-runtime",
- items: [
- {
- ...document.statusStrips[0].items[0],
- value: "Healthy",
- },
- ],
- });
- return document;
-}
-
-function applyJsonPatch(value: T, patch: JsonPatchOperation[]): T {
- const next = structuredClone(value);
- for (const operation of patch) {
- const { parent, key } = jsonPointerTarget(next, operation.path);
- if (operation.op === "remove") {
- if (Array.isArray(parent)) {
- parent.splice(Number(key), 1);
- } else {
- delete parent[key];
- }
- } else if (operation.op === "add") {
- if (Array.isArray(parent)) {
- parent.splice(Number(key), 0, operation.value);
- } else {
- parent[key] = operation.value;
- }
- } else if (operation.op === "replace") {
- if (Array.isArray(parent)) {
- parent[Number(key)] = operation.value;
- } else {
- parent[key] = operation.value;
- }
- }
- }
- return next;
-}
-
-function jsonPointerTarget(value: unknown, path: string) {
- const segments = path
- .split("/")
- .slice(1)
- .map((segment) => segment.replace(/~1/g, "/").replace(/~0/g, "~"));
- const key = segments.pop();
- if (key === undefined) throw new Error(`Invalid JSON pointer: ${path}`);
-
- let parent = value as Record | unknown[];
- for (const segment of segments) {
- parent = Array.isArray(parent)
- ? (parent[Number(segment)] as Record | unknown[])
- : (parent[segment] as Record | unknown[]);
- }
- return { parent, key };
-}
-
-function jsonRequest(body: unknown) {
- return new Request("https://dimensionlab.test/api/agent/dashboard", {
- body: JSON.stringify(body),
- headers: {
- authorization: "Bearer shared-secret",
- "content-type": "application/json",
- },
- method: "POST",
- });
-}
diff --git a/apps/web/src/lib/server/agent-config/index.ts b/apps/web/src/lib/server/agent-config/index.ts
deleted file mode 100644
index ab3d0ae..0000000
--- a/apps/web/src/lib/server/agent-config/index.ts
+++ /dev/null
@@ -1,1292 +0,0 @@
-import Ajv, { type ErrorObject } from "ajv";
-import addFormats from "ajv-formats";
-import { Type, type Static } from "@sinclair/typebox";
-import {
- DashboardDocumentSchema,
- DatasourceReferenceSchema,
- ServiceEntrySchema,
- TelemetryCardSchema,
- ThresholdSchema,
- validateDashboardDocument,
- type DashboardDocument,
- type DashboardModule,
- type DashboardValidationFailure,
- type DatasourceReference,
- type ServiceEntry,
- type ServiceGroup,
- type StatusItem,
- type TelemetryCard,
-} from "@dimensionlab/dashboard-model";
-import {
- createDashboardStore,
- DashboardRevisionNotFoundError,
- type DashboardRevision,
- type DashboardStore,
- type DashboardWriteMetadata,
-} from "$lib/server/db/dashboard-store";
-
-const IdentifierSchema = Type.String({
- minLength: 1,
- pattern: "^[a-z0-9][a-z0-9-_.:]*$",
-});
-
-const PositionSchema = Type.Object(
- {
- index: Type.Optional(Type.Integer({ minimum: 0 })),
- beforeId: Type.Optional(IdentifierSchema),
- afterId: Type.Optional(IdentifierSchema),
- },
- { additionalProperties: false, minProperties: 1 },
-);
-
-const AgentTargetSchema = Type.Union([
- Type.Object(
- {
- kind: Type.Literal("telemetry"),
- id: IdentifierSchema,
- },
- { additionalProperties: false },
- ),
- Type.Object(
- {
- kind: Type.Literal("service"),
- groupId: Type.Optional(IdentifierSchema),
- id: IdentifierSchema,
- },
- { additionalProperties: false },
- ),
- Type.Object(
- {
- kind: Type.Literal("serviceGroup"),
- id: IdentifierSchema,
- },
- { additionalProperties: false },
- ),
- Type.Object(
- {
- kind: Type.Literal("statusItem"),
- stripId: Type.Optional(IdentifierSchema),
- id: IdentifierSchema,
- },
- { additionalProperties: false },
- ),
- Type.Object(
- {
- kind: Type.Literal("module"),
- id: IdentifierSchema,
- },
- { additionalProperties: false },
- ),
-]);
-
-const AddSectionOperationSchema = Type.Object(
- {
- type: Type.Literal("add_section"),
- section: Type.Object(
- {
- id: IdentifierSchema,
- title: Type.String({ minLength: 1 }),
- layout: Type.Optional(Type.Union([Type.Literal("list"), Type.Literal("grid")])),
- },
- { additionalProperties: false },
- ),
- position: Type.Optional(PositionSchema),
- },
- { additionalProperties: false },
-);
-
-const AddServiceOperationSchema = Type.Object(
- {
- type: Type.Literal("add_service"),
- groupId: IdentifierSchema,
- service: ServiceEntrySchema,
- position: Type.Optional(PositionSchema),
- },
- { additionalProperties: false },
-);
-
-const AddMetricCardOperationSchema = Type.Object(
- {
- type: Type.Literal("add_metric_card"),
- card: TelemetryCardSchema,
- position: Type.Optional(PositionSchema),
- },
- { additionalProperties: false },
-);
-
-const ConnectDatasourceOperationSchema = Type.Object(
- {
- type: Type.Literal("connect_datasource"),
- target: AgentTargetSchema,
- datasource: DatasourceReferenceSchema,
- },
- { additionalProperties: false },
-);
-
-const SetStatusRuleOperationSchema = Type.Object(
- {
- type: Type.Literal("set_status_rule"),
- target: AgentTargetSchema,
- thresholds: Type.Optional(ThresholdSchema),
- severity: Type.Optional(
- Type.Union([
- Type.Literal("neutral"),
- Type.Literal("ok"),
- Type.Literal("warning"),
- Type.Literal("danger"),
- Type.Literal("stale"),
- Type.Literal("unavailable"),
- ]),
- ),
- detail: Type.Optional(Type.String()),
- value: Type.Optional(Type.String()),
- },
- { additionalProperties: false, minProperties: 3 },
-);
-
-const ArrangeItemOperationSchema = Type.Object(
- {
- type: Type.Literal("arrange_item"),
- area: Type.Union([
- Type.Literal("telemetry"),
- Type.Literal("serviceGroups"),
- Type.Literal("statusStrips"),
- Type.Literal("modules"),
- ]),
- id: IdentifierSchema,
- index: Type.Integer({ minimum: 0 }),
- },
- { additionalProperties: false },
-);
-
-const RemoveItemOperationSchema = Type.Object(
- {
- type: Type.Literal("remove_item"),
- target: AgentTargetSchema,
- },
- { additionalProperties: false },
-);
-
-const NestedDashboardDocumentSchema = Type.Unsafe(
- schemaWithoutRootId(DashboardDocumentSchema),
-);
-
-const CreateDashboardOperationSchema = Type.Object(
- {
- type: Type.Literal("create_dashboard"),
- document: NestedDashboardDocumentSchema,
- },
- { additionalProperties: false },
-);
-
-export const AgentDashboardOperationSchema = Type.Union([
- CreateDashboardOperationSchema,
- AddSectionOperationSchema,
- AddServiceOperationSchema,
- AddMetricCardOperationSchema,
- ConnectDatasourceOperationSchema,
- SetStatusRuleOperationSchema,
- ArrangeItemOperationSchema,
- RemoveItemOperationSchema,
-]);
-
-export const AgentDashboardOperationsSchema = Type.Array(AgentDashboardOperationSchema, {
- minItems: 1,
-});
-
-const PreviewRequestSchema = Type.Object(
- {
- action: Type.Literal("preview_changes"),
- operations: AgentDashboardOperationsSchema,
- },
- { additionalProperties: false },
-);
-
-const PublishRequestSchema = Type.Object(
- {
- action: Type.Literal("publish_changes"),
- actor: Type.Optional(Type.String({ minLength: 1 })),
- message: Type.Optional(Type.String({ minLength: 1 })),
- operations: AgentDashboardOperationsSchema,
- },
- { additionalProperties: false },
-);
-
-const RollbackRequestSchema = Type.Object(
- {
- action: Type.Literal("rollback_revision"),
- actor: Type.Optional(Type.String({ minLength: 1 })),
- message: Type.Optional(Type.String({ minLength: 1 })),
- revisionId: Type.String({ minLength: 1 }),
- },
- { additionalProperties: false },
-);
-
-export const AgentDashboardRequestSchema = Type.Union([
- PreviewRequestSchema,
- PublishRequestSchema,
- RollbackRequestSchema,
-]);
-
-export type AgentDashboardOperation = Static;
-export type AgentDashboardRequest = Static;
-
-export interface JsonPatchAddOperation {
- op: "add";
- path: string;
- value: unknown;
-}
-
-export interface JsonPatchRemoveOperation {
- op: "remove";
- path: string;
-}
-
-export interface JsonPatchReplaceOperation {
- op: "replace";
- path: string;
- value: unknown;
-}
-
-export type JsonPatchOperation =
- | JsonPatchAddOperation
- | JsonPatchRemoveOperation
- | JsonPatchReplaceOperation;
-
-export interface AgentConfigError {
- code: string;
- message: string;
- operationIndex?: number;
- path: string;
- details?: unknown;
-}
-
-export type AgentPreviewResult =
- | {
- ok: true;
- document: DashboardDocument;
- patch: JsonPatchOperation[];
- }
- | {
- ok: false;
- errors: AgentConfigError[];
- };
-
-export type AgentPublishResult =
- | {
- ok: true;
- document: DashboardDocument;
- patch: JsonPatchOperation[];
- previousRevisionId: string | null;
- revision: DashboardRevision;
- }
- | {
- ok: false;
- errors: AgentConfigError[];
- };
-
-export interface AgentDashboardHandlerOptions {
- store?: DashboardStore;
- token?: string;
-}
-
-export class AgentConfigAuthorizationError extends Error {
- constructor() {
- super("Unauthorized dashboard configuration request");
- this.name = "AgentConfigAuthorizationError";
- }
-}
-
-const validateOperations = createAjv().compile(
- AgentDashboardOperationsSchema,
-);
-const validateRequest = createAjv().compile(
- AgentDashboardRequestSchema,
-);
-
-function schemaWithoutRootId(schema: T): T {
- const clone = JSON.parse(JSON.stringify(schema)) as T & { $id?: string };
- delete clone.$id;
- return clone;
-}
-
-function createAjv() {
- return addFormats(
- new Ajv({
- allErrors: true,
- strict: false,
- strictNumbers: true,
- }),
- );
-}
-
-export function previewDashboardChanges(
- document: DashboardDocument,
- operations: AgentDashboardOperation[],
-): AgentPreviewResult {
- const operationValidation = validateAgentOperations(operations);
- if (operationValidation.length) return { ok: false, errors: operationValidation };
-
- const before = structuredClone(document);
- let next = structuredClone(document);
- const errors: AgentConfigError[] = [];
-
- operations.forEach((operation, operationIndex) => {
- if (errors.length) return;
- const result = applyOperation(next, operation, operationIndex);
- if (result.ok) {
- next = result.document;
- } else {
- errors.push(...result.errors);
- }
- });
-
- if (errors.length) return { ok: false, errors };
-
- const validation = validateDashboardDocument(next);
- if (!validation.valid) {
- return {
- ok: false,
- errors: dashboardValidationErrors(validation),
- };
- }
-
- return {
- ok: true,
- document: validation.data,
- patch: createJsonPatch(before, validation.data),
- };
-}
-
-export function publishDashboardChanges(
- store: DashboardStore,
- operations: AgentDashboardOperation[],
- metadata: DashboardWriteMetadata = {},
-): AgentPublishResult {
- const active = store.getActiveDashboard();
- if (!active) {
- const create = operations[0];
- if (!create || create.type !== "create_dashboard") {
- return {
- ok: false,
- errors: [
- {
- code: "no_active_dashboard",
- message: "No active dashboard exists; start with create_dashboard",
- path: "/",
- },
- ],
- };
- }
- }
-
- const base = active?.document || emptyDashboardDocument();
- const preview = previewDashboardChanges(base, operations);
- if (!preview.ok) return preview;
-
- const revision = store.commitDashboard(preview.document, metadata);
- return {
- ok: true,
- document: revision.document,
- patch: preview.patch,
- previousRevisionId: active?.currentRevisionId || null,
- revision,
- };
-}
-
-export function rollbackDashboardRevision(
- store: DashboardStore,
- revisionId: string,
- metadata: DashboardWriteMetadata = {},
-) {
- return store.rollbackToRevision(revisionId, metadata);
-}
-
-export function authorizeAgentConfigRequest(
- request: Request,
- token = process.env.AGENT_CONFIG_TOKEN,
-): { ok: true } {
- if (!token) throw new AgentConfigAuthorizationError();
-
- const authorization = request.headers.get("authorization") || "";
- const bearer = authorization.match(/^Bearer\s+(.+)$/i)?.[1];
- const headerToken = request.headers.get("x-agent-config-token");
- if (bearer === token || headerToken === token) return { ok: true };
-
- throw new AgentConfigAuthorizationError();
-}
-
-export async function handleAgentDashboardRequest(
- request: Request,
- options: AgentDashboardHandlerOptions = {},
-): Promise {
- const ownsStore = !options.store;
- const store = options.store || createDashboardStore();
-
- try {
- authorizeAgentConfigRequest(request, options.token);
- const body = await parseRequestBody(request);
- const requestValidationErrors = validateAgentRequest(body);
- if (requestValidationErrors.length) {
- return Response.json(
- { ok: false, errors: requestValidationErrors },
- { status: 400 },
- );
- }
-
- const agentRequest = body as AgentDashboardRequest;
- if (agentRequest.action === "rollback_revision") {
- const revision = rollbackDashboardRevision(store, agentRequest.revisionId, {
- actor: agentRequest.actor || "agent",
- message: agentRequest.message,
- });
- return Response.json({
- ok: true,
- action: agentRequest.action,
- revision: serializeRevision(revision),
- });
- }
-
- if (agentRequest.action === "preview_changes") {
- const active = store.getActiveDashboard();
- const base = active?.document || previewBaseDocument(agentRequest.operations);
- if (!base) {
- return Response.json(
- {
- ok: false,
- errors: [
- {
- code: "no_active_dashboard",
- message: "No active dashboard exists to preview against",
- path: "/",
- },
- ],
- },
- { status: 409 },
- );
- }
- const preview = previewDashboardChanges(base, agentRequest.operations);
- return Response.json(
- {
- ...preview,
- action: agentRequest.action,
- },
- { status: preview.ok ? 200 : 422 },
- );
- }
-
- const publish = publishDashboardChanges(store, agentRequest.operations, {
- actor: agentRequest.actor || "agent",
- message: agentRequest.message,
- });
- return Response.json(
- {
- ...publish,
- action: agentRequest.action,
- revision: publish.ok ? serializeRevision(publish.revision) : undefined,
- },
- { status: publish.ok ? 200 : 422 },
- );
- } catch (error) {
- if (error instanceof AgentConfigAuthorizationError) {
- return Response.json(
- {
- ok: false,
- errors: [
- {
- code: "unauthorized",
- message: error.message,
- path: "/",
- },
- ],
- },
- { status: 401 },
- );
- }
-
- if (error instanceof SyntaxError) {
- return Response.json(
- {
- ok: false,
- errors: [
- {
- code: "invalid_json",
- message: error.message,
- path: "/",
- },
- ],
- },
- { status: 400 },
- );
- }
-
- if (error instanceof DashboardRevisionNotFoundError) {
- return Response.json(
- {
- ok: false,
- errors: [
- {
- code: "revision_not_found",
- message: error.message,
- path: "/revisionId",
- },
- ],
- },
- { status: 404 },
- );
- }
-
- throw error;
- } finally {
- if (ownsStore) store.close();
- }
-}
-
-function validateAgentOperations(operations: unknown): AgentConfigError[] {
- if (!validateOperations(operations)) {
- return schemaErrors(validateOperations.errors || [], "invalid_operation_schema");
- }
-
- const createDashboardIndices = operations.flatMap((operation, index) =>
- operation.type === "create_dashboard" ? [index] : [],
- );
- if (createDashboardIndices.length > 1) {
- const operationIndex = createDashboardIndices[1];
- return [
- {
- code: "invalid_operation_sequence",
- message: "create_dashboard can only appear once",
- operationIndex,
- path: `/${operationIndex}`,
- },
- ];
- }
- if (createDashboardIndices[0] !== undefined && createDashboardIndices[0] !== 0) {
- const operationIndex = createDashboardIndices[0];
- return [
- {
- code: "invalid_operation_sequence",
- message: "create_dashboard must be the first operation",
- operationIndex,
- path: `/${operationIndex}`,
- },
- ];
- }
-
- return [];
-}
-
-function validateAgentRequest(value: unknown): AgentConfigError[] {
- if (validateRequest(value)) return [];
- return schemaErrors(validateRequest.errors || [], "invalid_request_schema");
-}
-
-function schemaErrors(errors: ErrorObject[], code: string): AgentConfigError[] {
- return errors.map((error) => ({
- code,
- message: error.message || "is invalid",
- operationIndex: operationIndexFromPath(error.instancePath),
- path: error.instancePath || "/",
- details: error.params,
- }));
-}
-
-function dashboardValidationErrors(
- failure: DashboardValidationFailure,
-): AgentConfigError[] {
- return failure.errors.map((message, index) => ({
- code: "dashboard_validation_failed",
- message,
- path: failure.details[index]?.instancePath || "/",
- details: failure.details[index]?.params,
- }));
-}
-
-function operationIndexFromPath(path: string): number | undefined {
- const segments = path.split("/").filter(Boolean);
- const operationsIndex = segments.indexOf("operations");
- const candidate =
- operationsIndex >= 0 ? segments[operationsIndex + 1] : segments[0];
- const index = Number(candidate);
- return Number.isInteger(index) ? index : undefined;
-}
-
-type ApplyResult =
- | { ok: true; document: DashboardDocument }
- | { ok: false; errors: AgentConfigError[] };
-
-function applyOperation(
- document: DashboardDocument,
- operation: AgentDashboardOperation,
- operationIndex: number,
-): ApplyResult {
- const next = structuredClone(document);
-
- switch (operation.type) {
- case "create_dashboard":
- return { ok: true, document: structuredClone(operation.document) };
- case "add_section":
- return addSection(next, operation, operationIndex);
- case "add_service":
- return addService(next, operation, operationIndex);
- case "add_metric_card":
- return addMetricCard(next, operation, operationIndex);
- case "connect_datasource":
- return connectDatasource(next, operation, operationIndex);
- case "set_status_rule":
- return setStatusRule(next, operation, operationIndex);
- case "arrange_item":
- return arrangeItem(next, operation, operationIndex);
- case "remove_item":
- return removeItem(next, operation, operationIndex);
- }
-}
-
-type AddSectionOperation = Extract;
-type AddServiceOperation = Extract;
-type AddMetricCardOperation = Extract;
-type ConnectDatasourceOperation = Extract;
-type SetStatusRuleOperation = Extract;
-type ArrangeItemOperation = Extract;
-type RemoveItemOperation = Extract;
-
-function addSection(
- document: DashboardDocument,
- operation: AddSectionOperation,
- operationIndex: number,
-): ApplyResult {
- if (document.serviceGroups.some((group) => group.id === operation.section.id)) {
- return failure(
- "duplicate_id",
- `Service group already exists: ${operation.section.id}`,
- operationIndex,
- "/serviceGroups",
- );
- }
-
- const group: ServiceGroup = {
- ...operation.section,
- services: [],
- };
- document.serviceGroups.push(group);
- document.layout.serviceGroups = insertId(
- document.layout.serviceGroups,
- group.id,
- operation.position,
- );
- return { ok: true, document };
-}
-
-function addService(
- document: DashboardDocument,
- operation: AddServiceOperation,
- operationIndex: number,
-): ApplyResult {
- const group = document.serviceGroups.find((item) => item.id === operation.groupId);
- if (!group) {
- return failure(
- "group_not_found",
- `Service group not found: ${operation.groupId}`,
- operationIndex,
- "/serviceGroups",
- );
- }
-
- if (
- document.serviceGroups.some((item) =>
- item.services.some((service) => service.id === operation.service.id),
- )
- ) {
- return failure(
- "duplicate_id",
- `Service already exists: ${operation.service.id}`,
- operationIndex,
- "/serviceGroups",
- );
- }
-
- group.services = insertItem(group.services, operation.service, operation.position);
- return { ok: true, document };
-}
-
-function addMetricCard(
- document: DashboardDocument,
- operation: AddMetricCardOperation,
- operationIndex: number,
-): ApplyResult {
- if (document.telemetry.some((card) => card.id === operation.card.id)) {
- return failure(
- "duplicate_id",
- `Telemetry card already exists: ${operation.card.id}`,
- operationIndex,
- "/telemetry",
- );
- }
-
- document.telemetry.push(operation.card);
- document.layout.telemetry = insertId(
- document.layout.telemetry,
- operation.card.id,
- operation.position,
- );
- return { ok: true, document };
-}
-
-function connectDatasource(
- document: DashboardDocument,
- operation: ConnectDatasourceOperation,
- operationIndex: number,
-): ApplyResult {
- if (!supportsDatasource(operation.target.kind)) {
- return failure(
- "unsupported_target",
- `Target does not support datasources: ${operation.target.kind}`,
- operationIndex,
- targetPath(operation.target),
- );
- }
-
- const targetResolution = resolveTarget(document, operation.target, operationIndex);
- if (!targetResolution.ok) {
- return { ok: false, errors: targetResolution.errors };
- }
-
- const target = targetResolution.target;
- (target as DatasourceAgentTarget).datasource = operation.datasource;
- return { ok: true, document };
-}
-
-function setStatusRule(
- document: DashboardDocument,
- operation: SetStatusRuleOperation,
- operationIndex: number,
-): ApplyResult {
- if (operation.target.kind === "serviceGroup") {
- return failure(
- "unsupported_target",
- `Target does not support status rules: ${operation.target.kind}`,
- operationIndex,
- targetPath(operation.target),
- );
- }
- if (operation.thresholds && operation.target.kind !== "telemetry") {
- return failure(
- "unsupported_target",
- "Thresholds can only be set on telemetry targets",
- operationIndex,
- targetPath(operation.target),
- );
- }
- if (operation.detail !== undefined && !supportsDetail(operation.target.kind)) {
- return failure(
- "unsupported_target",
- "Detail can only be set on telemetry, service, or module targets",
- operationIndex,
- targetPath(operation.target),
- );
- }
- if (operation.value !== undefined && !supportsStringValue(operation.target.kind)) {
- return failure(
- "unsupported_target",
- "String values can only be set on status item or module targets",
- operationIndex,
- targetPath(operation.target),
- );
- }
-
- const targetResolution = resolveTarget(document, operation.target, operationIndex);
- if (!targetResolution.ok) {
- return { ok: false, errors: targetResolution.errors };
- }
-
- const target = targetResolution.target;
- if (operation.thresholds && operation.target.kind === "telemetry") {
- (target as TelemetryCard).thresholds = operation.thresholds;
- }
- if (operation.severity) {
- target.severity = operation.severity;
- }
- if (operation.detail !== undefined && supportsDetail(operation.target.kind)) {
- (target as DetailAgentTarget).detail = operation.detail;
- }
- if (operation.value !== undefined && supportsStringValue(operation.target.kind)) {
- (target as StringValueAgentTarget).value = operation.value;
- }
-
- return { ok: true, document };
-}
-
-function arrangeItem(
- document: DashboardDocument,
- operation: ArrangeItemOperation,
- operationIndex: number,
-): ApplyResult {
- const list = layoutList(document, operation.area);
- if (!list.includes(operation.id)) {
- return failure(
- "item_not_found",
- `Layout item not found: ${operation.id}`,
- operationIndex,
- `/layout/${operation.area}`,
- );
- }
-
- const without = list.filter((id) => id !== operation.id);
- const index = Math.min(operation.index, without.length);
- without.splice(index, 0, operation.id);
- setLayoutList(document, operation.area, without);
- syncCollectionOrder(document, operation.area, without);
- return { ok: true, document };
-}
-
-function removeItem(
- document: DashboardDocument,
- operation: RemoveItemOperation,
- operationIndex: number,
-): ApplyResult {
- if (operation.target.kind === "telemetry") {
- if (!document.telemetry.some((card) => card.id === operation.target.id)) {
- return failure(
- "item_not_found",
- `Telemetry card not found: ${operation.target.id}`,
- operationIndex,
- "/telemetry",
- );
- }
- document.telemetry = document.telemetry.filter((card) => card.id !== operation.target.id);
- document.layout.telemetry = document.layout.telemetry.filter((id) => id !== operation.target.id);
- return { ok: true, document };
- }
-
- if (operation.target.kind === "serviceGroup") {
- if (!document.serviceGroups.some((group) => group.id === operation.target.id)) {
- return failure(
- "item_not_found",
- `Service group not found: ${operation.target.id}`,
- operationIndex,
- "/serviceGroups",
- );
- }
- document.serviceGroups = document.serviceGroups.filter((group) => group.id !== operation.target.id);
- document.layout.serviceGroups = document.layout.serviceGroups.filter((id) => id !== operation.target.id);
- return { ok: true, document };
- }
-
- if (operation.target.kind === "service") {
- const matches = findServices(document, operation.target.id, operation.target.groupId);
- if (!matches.length) {
- return failure(
- "item_not_found",
- `Service not found: ${operation.target.id}`,
- operationIndex,
- "/serviceGroups",
- );
- }
- if (!operation.target.groupId && matches.length > 1) {
- return failure(
- "ambiguous_target",
- `Service exists in multiple groups: ${operation.target.id}`,
- operationIndex,
- "/serviceGroups",
- );
- }
-
- const match = matches[0];
- match.group.services = match.group.services.filter((service) => service.id !== operation.target.id);
- return { ok: true, document };
- }
-
- if (operation.target.kind === "module") {
- if (!(document.modules || []).some((module) => module.id === operation.target.id)) {
- return failure(
- "item_not_found",
- `Module not found: ${operation.target.id}`,
- operationIndex,
- "/modules",
- );
- }
- document.modules = (document.modules || []).filter((module) => module.id !== operation.target.id);
- document.layout.modules = (document.layout.modules || []).filter((id) => id !== operation.target.id);
- return { ok: true, document };
- }
-
- if (operation.target.kind === "statusItem") {
- const matches = findStatusItems(document, operation.target.id, operation.target.stripId);
- if (!matches.length) {
- return failure(
- "item_not_found",
- `Status item not found: ${operation.target.id}`,
- operationIndex,
- "/statusStrips",
- );
- }
- if (!operation.target.stripId && matches.length > 1) {
- return failure(
- "ambiguous_target",
- `Status item exists in multiple strips: ${operation.target.id}`,
- operationIndex,
- "/statusStrips",
- );
- }
-
- const [match] = matches;
- match.strip.items = match.strip.items.filter((item) => item.id !== operation.target.id);
- return { ok: true, document };
- }
-
- return failure("unsupported_target", "Cannot remove target kind", operationIndex, "/");
-}
-
-function resolveTarget(
- document: DashboardDocument,
- target: ConnectDatasourceOperation["target"],
- operationIndex: number,
-): ResolveTargetResult {
- if (target.kind === "telemetry") {
- const card = document.telemetry.find((item) => item.id === target.id);
- if (!card) {
- return resolutionFailure(
- "item_not_found",
- `Telemetry card not found: ${target.id}`,
- operationIndex,
- "/telemetry",
- );
- }
- return { ok: true, target: card };
- }
- if (target.kind === "service") {
- const matches = findServices(document, target.id, target.groupId);
- if (!matches.length) {
- return resolutionFailure(
- "item_not_found",
- `Service not found: ${target.id}`,
- operationIndex,
- "/serviceGroups",
- );
- }
- if (!target.groupId && matches.length > 1) {
- return resolutionFailure(
- "ambiguous_target",
- `Service exists in multiple groups: ${target.id}`,
- operationIndex,
- "/serviceGroups",
- );
- }
- return { ok: true, target: matches[0].service };
- }
- if (target.kind === "statusItem") {
- const matches = findStatusItems(document, target.id, target.stripId);
- if (!matches.length) {
- return resolutionFailure(
- "item_not_found",
- `Status item not found: ${target.id}`,
- operationIndex,
- "/statusStrips",
- );
- }
- if (!target.stripId && matches.length > 1) {
- return resolutionFailure(
- "ambiguous_target",
- `Status item exists in multiple strips: ${target.id}`,
- operationIndex,
- "/statusStrips",
- );
- }
- return { ok: true, target: matches[0].item };
- }
- if (target.kind === "module") {
- const module = (document.modules || []).find((item) => item.id === target.id);
- if (!module) {
- return resolutionFailure(
- "item_not_found",
- `Module not found: ${target.id}`,
- operationIndex,
- "/modules",
- );
- }
- return { ok: true, target: module };
- }
- return resolutionFailure(
- "item_not_found",
- `Target not found: ${target.id}`,
- operationIndex,
- targetPath(target),
- );
-}
-
-type ResolveTargetResult =
- | { ok: true; target: ResolvedAgentTarget }
- | { ok: false; errors: AgentConfigError[] };
-type ResolvedAgentTarget = TelemetryCard | ServiceEntry | StatusItem | DashboardModule;
-type DatasourceAgentTarget = TelemetryCard | ServiceEntry | DashboardModule;
-type DetailAgentTarget = TelemetryCard | ServiceEntry | DashboardModule;
-type StringValueAgentTarget = StatusItem | DashboardModule;
-
-type DatasourceTargetKind = Extract<
- ConnectDatasourceOperation["target"]["kind"],
- "telemetry" | "service" | "module"
->;
-
-function supportsDatasource(
- kind: ConnectDatasourceOperation["target"]["kind"],
-): kind is DatasourceTargetKind {
- return kind === "telemetry" || kind === "service" || kind === "module";
-}
-
-type DetailTargetKind = Extract<
- SetStatusRuleOperation["target"]["kind"],
- "telemetry" | "service" | "module"
->;
-
-function supportsDetail(
- kind: SetStatusRuleOperation["target"]["kind"],
-): kind is DetailTargetKind {
- return kind === "telemetry" || kind === "service" || kind === "module";
-}
-
-type StringValueTargetKind = Extract<
- SetStatusRuleOperation["target"]["kind"],
- "statusItem" | "module"
->;
-
-function supportsStringValue(
- kind: SetStatusRuleOperation["target"]["kind"],
-): kind is StringValueTargetKind {
- return kind === "statusItem" || kind === "module";
-}
-
-function findServices(document: DashboardDocument, id: string, groupId?: string) {
- const groups = groupId
- ? document.serviceGroups.filter((group) => group.id === groupId)
- : document.serviceGroups;
- return groups.flatMap((group) =>
- group.services.flatMap((service) =>
- service.id === id ? [{ group, service }] : [],
- ),
- );
-}
-
-function findStatusItems(document: DashboardDocument, id: string, stripId?: string) {
- const strips = stripId
- ? document.statusStrips.filter((strip) => strip.id === stripId)
- : document.statusStrips;
- return strips.flatMap((strip) =>
- strip.items.flatMap((item) => (item.id === id ? [{ strip, item }] : [])),
- );
-}
-
-function resolutionFailure(
- code: string,
- message: string,
- operationIndex: number,
- path: string,
-): ResolveTargetResult {
- return {
- ok: false,
- errors: [
- {
- code,
- message,
- operationIndex,
- path,
- },
- ],
- };
-}
-
-function targetPath(target: ConnectDatasourceOperation["target"]): string {
- if (target.kind === "telemetry") return "/telemetry";
- if (target.kind === "service") return "/serviceGroups";
- if (target.kind === "serviceGroup") return "/serviceGroups";
- if (target.kind === "statusItem") return "/statusStrips";
- return "/modules";
-}
-
-function layoutList(document: DashboardDocument, area: ArrangeItemOperation["area"]) {
- if (area === "modules") return document.layout.modules || [];
- return document.layout[area];
-}
-
-function setLayoutList(
- document: DashboardDocument,
- area: ArrangeItemOperation["area"],
- value: string[],
-) {
- if (area === "modules") {
- document.layout.modules = value;
- return;
- }
- document.layout[area] = value;
-}
-
-function syncCollectionOrder(
- document: DashboardDocument,
- area: ArrangeItemOperation["area"],
- orderedIds: string[],
-) {
- if (area === "telemetry") {
- document.telemetry = orderByIds(document.telemetry, orderedIds);
- return;
- }
- if (area === "serviceGroups") {
- document.serviceGroups = orderByIds(document.serviceGroups, orderedIds);
- return;
- }
- if (area === "statusStrips") {
- document.statusStrips = orderByIds(document.statusStrips, orderedIds);
- return;
- }
- document.modules = orderByIds(document.modules || [], orderedIds);
-}
-
-function orderByIds(items: T[], orderedIds: string[]): T[] {
- const order = new Map(orderedIds.map((id, index) => [id, index]));
- return [...items].sort(
- (left, right) =>
- (order.get(left.id) ?? Number.MAX_SAFE_INTEGER) -
- (order.get(right.id) ?? Number.MAX_SAFE_INTEGER),
- );
-}
-
-function insertItem(
- items: T[],
- item: T,
- position?: Static,
-): T[] {
- const next = [...items];
- next.splice(resolveInsertionIndex(next.map((entry) => entry.id), position), 0, item);
- return next;
-}
-
-function insertId(
- ids: string[],
- id: string,
- position?: Static,
-): string[] {
- const next = [...ids];
- next.splice(resolveInsertionIndex(next, position), 0, id);
- return next;
-}
-
-function resolveInsertionIndex(ids: string[], position?: Static) {
- if (!position) return ids.length;
- if (typeof position.index === "number") return Math.min(position.index, ids.length);
- if (position.beforeId) {
- const beforeIndex = ids.indexOf(position.beforeId);
- return beforeIndex >= 0 ? beforeIndex : ids.length;
- }
- if (position.afterId) {
- const afterIndex = ids.indexOf(position.afterId);
- return afterIndex >= 0 ? afterIndex + 1 : ids.length;
- }
- return ids.length;
-}
-
-function failure(
- code: string,
- message: string,
- operationIndex: number,
- path: string,
-): ApplyResult {
- return {
- ok: false,
- errors: [
- {
- code,
- message,
- operationIndex,
- path,
- },
- ],
- };
-}
-
-function createJsonPatch(
- before: unknown,
- after: unknown,
- path = "",
-): JsonPatchOperation[] {
- if (before === undefined) return [{ op: "add", path: path || "/", value: after }];
- if (after === undefined) return [{ op: "remove", path: path || "/" }];
- if (Object.is(before, after)) return [];
-
- if (!isObjectLike(before) || !isObjectLike(after)) {
- return [{ op: "replace", path: path || "/", value: after }];
- }
-
- if (Array.isArray(before) || Array.isArray(after)) {
- if (!Array.isArray(before) || !Array.isArray(after)) {
- return [{ op: "replace", path: path || "/", value: after }];
- }
- const operations: JsonPatchOperation[] = [];
- const sharedLength = Math.min(before.length, after.length);
- for (let index = 0; index < sharedLength; index += 1) {
- operations.push(
- ...createJsonPatch(before[index], after[index], `${path}/${index}`),
- );
- }
- for (let index = before.length - 1; index >= after.length; index -= 1) {
- operations.push({ op: "remove", path: `${path}/${index}` });
- }
- for (let index = before.length; index < after.length; index += 1) {
- operations.push({ op: "add", path: `${path}/${index}`, value: after[index] });
- }
- return operations;
- }
-
- const beforeObject = before as Record;
- const afterObject = after as Record;
- const keys = new Set([...Object.keys(beforeObject), ...Object.keys(afterObject)]);
- return [...keys].flatMap((key) =>
- createJsonPatch(beforeObject[key], afterObject[key], `${path}/${escapeJsonPointer(key)}`),
- );
-}
-
-function isObjectLike(value: unknown): value is Record | unknown[] {
- return typeof value === "object" && value !== null;
-}
-
-function escapeJsonPointer(value: string): string {
- return value.replace(/~/g, "~0").replace(/\//g, "~1");
-}
-
-async function parseRequestBody(request: Request): Promise {
- const text = await request.text();
- return text ? JSON.parse(text) : {};
-}
-
-function previewBaseDocument(operations: AgentDashboardOperation[]): DashboardDocument | null {
- return operations[0]?.type === "create_dashboard" ? emptyDashboardDocument() : null;
-}
-
-function serializeRevision(revision: DashboardRevision) {
- return {
- id: revision.id,
- dashboardId: revision.dashboardId,
- schemaVersion: revision.schemaVersion,
- actor: revision.actor,
- message: revision.message,
- operation: revision.operation,
- sourceRevisionId: revision.sourceRevisionId,
- createdAt: revision.createdAt.toISOString(),
- };
-}
-
-function emptyDashboardDocument(): DashboardDocument {
- return {
- schemaVersion: "dashboard.v1",
- metadata: {
- title: "Untitled Dashboard",
- },
- layout: {
- telemetry: [],
- serviceGroups: [],
- statusStrips: [],
- modules: [],
- },
- telemetry: [],
- serviceGroups: [],
- statusStrips: [],
- modules: [],
- };
-}
diff --git a/apps/web/src/lib/server/datasources/dashboard-datasources.test.ts b/apps/web/src/lib/server/datasources/dashboard-datasources.test.ts
deleted file mode 100644
index d24b48b..0000000
--- a/apps/web/src/lib/server/datasources/dashboard-datasources.test.ts
+++ /dev/null
@@ -1,334 +0,0 @@
-import { describe, expect, test, vi } from "vitest";
-import {
- DASHBOARD_SCHEMA_VERSION,
- type DashboardDocument,
-} from "@dimensionlab/dashboard-model";
-import { resolveDashboardDatasources, resolveDashboardTile } from ".";
-
-describe("dashboard datasource resolution", () => {
- test("hydrates telemetry, service health, weather, and summary data from live adapters", async () => {
- const fetch = vi.fn(async (input: RequestInfo | URL) => {
- const url = String(input);
-
- if (url.startsWith("https://prometheus.example/api/v1/query_range")) {
- return jsonResponse({
- status: "success",
- data: {
- result: [
- {
- metric: { host: "linux-infra" },
- values: [
- [1771430000, "10"],
- [1771430060, "40"],
- [1771430120, "30"],
- ],
- },
- {
- metric: { host: "linux-gpu" },
- values: [
- [1771430000, "12"],
- [1771430060, "24"],
- [1771430120, "36"],
- ],
- },
- ],
- },
- });
- }
-
- if (url.startsWith("https://prometheus.example/api/v1/query")) {
- const query = new URL(url).searchParams.get("query") || "";
- if (query.includes("node_boot_time_seconds")) {
- return prometheusVector("90061");
- }
- if (query.includes("node_load15")) return prometheusVector("0.40");
- if (query.includes("node_load5")) return prometheusVector("0.46");
- if (query.includes("node_load1")) return prometheusVector("0.34");
-
- return jsonResponse({
- status: "success",
- data: {
- result: [
- {
- metric: { host: "linux-infra", mountpoint: "/home" },
- value: [1771430400, "88"],
- },
- ],
- },
- });
- }
-
- if (url.startsWith("https://api.open-meteo.com/v1/forecast")) {
- return jsonResponse({
- current: {
- apparent_temperature: 20.9,
- temperature_2m: 21.4,
- weather_code: 0,
- wind_speed_10m: 12,
- },
- });
- }
-
- if (url === "https://service.example/health") {
- return jsonResponse({
- status: "UP",
- ping: 42,
- });
- }
-
- throw new Error(`Unhandled test request: ${url}`);
- });
-
- const resolved = await resolveDashboardDatasources(testDashboard(), {
- fetch,
- prometheusBaseUrl: "https://prometheus.example",
- });
-
- expect(fetch).toHaveBeenCalledWith(
- expect.stringContaining("/api/v1/query?"),
- expect.objectContaining({ cache: "no-store" }),
- );
- expect(fetch).toHaveBeenCalledWith(
- expect.stringContaining("/api/v1/query_range?"),
- expect.objectContaining({ cache: "no-store" }),
- );
-
- const telemetry = resolved.telemetry[0];
- expect(telemetry.value).toEqual({ kind: "percent", value: 88 });
- expect(telemetry.severity).toBe("warning");
- expect(telemetry.detail).toBe("linux-infra /home");
- expect(telemetry.sparkline).toEqual([12, 40, 36]);
-
- const service = resolved.serviceGroups[0].services[0];
- expect(service.severity).toBe("ok");
- expect(service.detail).toBe("42 ms");
-
- const weather = resolved.modules?.find((module) => module.id === "weather-amsterdam");
- expect(weather?.value).toBe("21.4 C");
- expect(weather?.detail).toBe("Clear - feels 20.9 C - wind 12 km/h");
- expect(weather?.severity).toBe("ok");
-
- const summary = resolved.modules?.find((module) => module.id === "runtime-health-summary");
- expect(summary?.value).toBe("all systems operational");
- expect(summary?.detail).toBe("1 service ok");
- expect(summary?.severity).toBe("ok");
-
- expect(resolved.statusStrips[0].items).toEqual([
- { id: "system-status", label: "System Status", value: "All systems operational", severity: "ok" },
- { id: "last-sync", label: "Last Sync", value: "just now", severity: "ok" },
- { id: "uptime", label: "Uptime", value: "1d 1h 1m", severity: "ok" },
- { id: "load-avg", label: "Load Avg", value: "0.34 0.46 0.40", severity: "neutral" },
- { id: "auto-refresh", label: "Auto Refresh", value: "15s", severity: "neutral" },
- ]);
-
- expect(resolved).not.toBe(testDocument);
- expect(testDocument.telemetry[0].value.value).toBe(1);
- });
-
- test("shares service health snapshots across aggregate tile hydration", async () => {
- let resolveFetch: ((response: Response) => void) | undefined;
- const fetch = vi.fn((input: RequestInfo | URL) => {
- const url = String(input);
- if (url !== "https://service.example/health") {
- throw new Error(`Unhandled test request: ${url}`);
- }
-
- return new Promise((resolve) => {
- resolveFetch = resolve;
- });
- });
- const document = testDashboard();
-
- const moduleTile = resolveDashboardTile(
- document,
- { kind: "module", id: "runtime-health-summary" },
- { fetch },
- );
- const statusTile = resolveDashboardTile(
- document,
- { kind: "status", stripId: "footer", id: "system-status" },
- { fetch },
- );
-
- await Promise.resolve();
- expect(fetch).toHaveBeenCalledTimes(1);
-
- resolveFetch?.(jsonResponse({ status: "UP", ping: 42 }));
- expect(await moduleTile).toMatchObject({
- state: "ready",
- item: {
- id: "runtime-health-summary",
- severity: "ok",
- value: "all systems operational",
- },
- });
- expect(await statusTile).toMatchObject({
- state: "ready",
- item: {
- id: "system-status",
- severity: "ok",
- value: "All systems operational",
- },
- });
- });
-
- test("isolates service health snapshots by datasource fetch context", async () => {
- const firstFetch = vi.fn(async () =>
- jsonResponse({
- status: "UP",
- ping: 42,
- })
- );
- const secondFetch = vi.fn(async () =>
- jsonResponse({
- status: "DOWN",
- ping: 0,
- })
- );
- const document = testDashboard();
- document.serviceGroups[0].services[0] = {
- ...document.serviceGroups[0].services[0],
- id: "api-isolated",
- datasource: {
- type: "external",
- adapter: "http-status",
- reference: "GET https://service.example/isolated",
- },
- };
-
- await resolveDashboardTile(
- document,
- { kind: "status", stripId: "footer", id: "system-status" },
- { fetch: firstFetch },
- );
- const second = await resolveDashboardTile(
- document,
- { kind: "status", stripId: "footer", id: "system-status" },
- { fetch: secondFetch },
- );
-
- expect(firstFetch).toHaveBeenCalledTimes(1);
- expect(secondFetch).toHaveBeenCalledTimes(1);
- expect(second).toMatchObject({
- state: "ready",
- item: {
- id: "system-status",
- severity: "danger",
- value: "1 service down",
- },
- });
- });
-});
-
-const testDocument = testDashboard();
-
-function testDashboard(): DashboardDocument {
- return {
- schemaVersion: DASHBOARD_SCHEMA_VERSION,
- metadata: {
- title: "System Overview",
- refreshIntervalSeconds: 15,
- },
- layout: {
- telemetry: ["cpu"],
- serviceGroups: ["services"],
- statusStrips: ["footer"],
- modules: ["weather-amsterdam", "runtime-health-summary"],
- },
- telemetry: [
- {
- id: "cpu",
- label: "CPU",
- value: { kind: "percent", value: 1 },
- detail: "fallback",
- severity: "stale",
- thresholds: { warning: 70, danger: 90 },
- datasource: {
- type: "external",
- adapter: "prometheus",
- reference: "fixture_cpu_query",
- },
- sparkline: [1],
- },
- ],
- serviceGroups: [
- {
- id: "services",
- title: "Services",
- services: [
- {
- id: "api",
- label: "API",
- description: "Example API",
- severity: "stale",
- detail: "fallback",
- datasource: {
- type: "external",
- adapter: "http-status",
- reference: "GET https://service.example/health",
- },
- },
- ],
- },
- ],
- statusStrips: [
- {
- id: "footer",
- items: [
- { id: "system-status", label: "System Status", value: "fallback", severity: "ok" },
- { id: "last-sync", label: "Last Sync", value: "fallback", severity: "stale" },
- { id: "uptime", label: "Uptime", value: "fallback", severity: "ok" },
- { id: "load-avg", label: "Load Avg", value: "fallback", severity: "neutral" },
- { id: "auto-refresh", label: "Auto Refresh", value: "fallback", severity: "neutral" },
- ],
- },
- ],
- modules: [
- {
- id: "weather-amsterdam",
- kind: "weather",
- title: "Amsterdam",
- value: "fallback",
- detail: "fallback",
- severity: "stale",
- datasource: {
- type: "external",
- adapter: "weather",
- reference: "open-meteo:latitude=52.3676&longitude=4.9041",
- },
- },
- {
- id: "runtime-health-summary",
- kind: "summary",
- title: "Runtime Health",
- value: "fallback",
- detail: "fallback",
- severity: "stale",
- datasource: {
- type: "placeholder",
- reason: "summary pending live health aggregation",
- },
- },
- ],
- };
-}
-
-function jsonResponse(payload: unknown): Response {
- return new Response(JSON.stringify(payload), {
- headers: { "content-type": "application/json" },
- });
-}
-
-function prometheusVector(value: string): Response {
- return jsonResponse({
- status: "success",
- data: {
- result: [
- {
- metric: { host: "linux-infra" },
- value: [1771430400, value],
- },
- ],
- },
- });
-}
diff --git a/apps/web/src/lib/server/datasources/index.ts b/apps/web/src/lib/server/datasources/index.ts
deleted file mode 100644
index 62df13a..0000000
--- a/apps/web/src/lib/server/datasources/index.ts
+++ /dev/null
@@ -1,919 +0,0 @@
-import type {
- DashboardDocument,
- DashboardModule,
- MetricValue,
- ServiceEntry,
- ServiceGroup,
- Severity,
- StatusItem,
- StatusStrip,
- TelemetryCard,
-} from "@dimensionlab/dashboard-model";
-
-export type DashboardTileReference =
- | { kind: "telemetry"; id: string }
- | { kind: "service"; groupId: string; id: string }
- | { kind: "module"; id: string }
- | { kind: "status"; stripId: string; id: string };
-
-export type DashboardTileItem =
- | DashboardModule
- | ServiceEntry
- | StatusItem
- | TelemetryCard;
-
-export type DashboardTileResolution =
- | {
- state: "ready";
- tile: DashboardTileReference;
- item: DashboardTileItem;
- }
- | {
- state: "not_found";
- tile: DashboardTileReference;
- message: string;
- }
- | {
- state: "disabled";
- tile: DashboardTileReference;
- message: string;
- };
-
-export interface DatasourceResolutionOptions {
- fetch?: DatasourceFetch;
- now?: () => number;
- prometheusBaseUrl?: string;
- prometheusRangeSeconds?: number;
- prometheusStepSeconds?: number;
- requestTimeoutMs?: number;
-}
-
-export async function resolveDashboardDatasources(
- document: DashboardDocument,
- options: DatasourceResolutionOptions = {},
-): Promise {
- const context = datasourceContext(options);
- const telemetry = await Promise.all(
- document.telemetry.map((card) => resolveTelemetryCard(card, context)),
- );
- const serviceGroups = await Promise.all(
- document.serviceGroups.map((group) => resolveServiceGroup(group, context)),
- );
- const modules = await resolveModules(document.modules || [], serviceGroups, context);
- const statusStrips = await resolveStatusStrips(
- document.statusStrips,
- document.metadata.refreshIntervalSeconds,
- serviceGroups,
- context,
- );
-
- return {
- ...structuredClone(document),
- telemetry,
- serviceGroups,
- modules,
- statusStrips,
- };
-}
-
-export async function resolveDashboardTile(
- document: DashboardDocument,
- tile: DashboardTileReference,
- options: DatasourceResolutionOptions = {},
-): Promise {
- const context = datasourceContext(options);
-
- if (tile.kind === "telemetry") {
- const card = document.telemetry.find((item) => item.id === tile.id);
- if (!card) return missingTile(tile);
-
- return {
- state: "ready",
- tile,
- item: await resolveTelemetryCard(card, context),
- };
- }
-
- if (tile.kind === "service") {
- const service = document.serviceGroups
- .find((group) => group.id === tile.groupId)
- ?.services.find((item) => item.id === tile.id);
- if (!service) return missingTile(tile);
-
- return {
- state: "ready",
- tile,
- item: await resolveService(service, context),
- };
- }
-
- if (tile.kind === "module") {
- const module = document.modules?.find((item) => item.id === tile.id);
- if (!module) return missingTile(tile);
-
- const item = module.id === "runtime-health-summary"
- ? runtimeHealthSummary(
- module,
- await serviceGroupsSnapshot(document, context),
- )
- : await resolveModule(module, context);
-
- return { state: "ready", tile, item };
- }
-
- const strip = document.statusStrips.find((item) => item.id === tile.stripId);
- const statusItem = strip?.items.find((item) => item.id === tile.id);
- if (!strip || !statusItem) return missingTile(tile);
-
- return {
- state: "ready",
- tile,
- item: await resolveStatusTile(
- statusItem,
- document.metadata.refreshIntervalSeconds,
- document,
- context,
- ),
- };
-}
-
-interface DatasourceContext {
- fetch: DatasourceFetch;
- fetchIdentity: number;
- now: () => number;
- prometheusBaseUrl: string;
- prometheusRangeSeconds: number;
- prometheusStepSeconds: number;
- requestTimeoutMs: number;
- serviceGroupsSnapshot?: Promise;
-}
-
-type DatasourceFetch = (input: string, init?: RequestInit) => Promise;
-
-interface ServiceGroupsSnapshotEntry {
- expiresAt: number;
- snapshot: Promise;
-}
-
-const serviceGroupsSnapshotTtlMs = 30_000;
-const serviceGroupsSnapshotCache = new Map();
-const datasourceFetchIdentities = new WeakMap();
-let nextDatasourceFetchIdentity = 1;
-
-interface PrometheusVectorResult {
- metric?: Record;
- value?: [number, string];
-}
-
-interface PrometheusMatrixResult {
- metric?: Record;
- values?: Array<[number, string]>;
-}
-
-function datasourceContext(options: DatasourceResolutionOptions): DatasourceContext {
- const fetch = options.fetch || globalThis.fetch;
-
- return {
- fetch,
- fetchIdentity: datasourceFetchIdentity(fetch),
- now: options.now || Date.now,
- prometheusBaseUrl:
- options.prometheusBaseUrl ||
- process.env.PROMETHEUS_BASE_URL ||
- "https://prometheus.dimensionlab.net",
- prometheusRangeSeconds: options.prometheusRangeSeconds || 60 * 60,
- prometheusStepSeconds: options.prometheusStepSeconds || 120,
- requestTimeoutMs: options.requestTimeoutMs || 2_500,
- };
-}
-
-function datasourceFetchIdentity(fetch: DatasourceFetch): number {
- const existing = datasourceFetchIdentities.get(fetch);
- if (existing) return existing;
-
- const next = nextDatasourceFetchIdentity;
- nextDatasourceFetchIdentity += 1;
- datasourceFetchIdentities.set(fetch, next);
- return next;
-}
-
-function serviceGroupsSnapshot(
- document: DashboardDocument,
- context: DatasourceContext,
-): Promise {
- if (context.serviceGroupsSnapshot) return context.serviceGroupsSnapshot;
-
- const key = serviceGroupsSnapshotKey(document, context);
- const now = context.now();
- const cached = serviceGroupsSnapshotCache.get(key);
- if (cached && cached.expiresAt > now) {
- context.serviceGroupsSnapshot = cached.snapshot;
- return cached.snapshot;
- }
-
- const snapshot = Promise.all(
- document.serviceGroups.map((group) => resolveServiceGroup(group, context)),
- );
- context.serviceGroupsSnapshot = snapshot;
- serviceGroupsSnapshotCache.set(key, {
- expiresAt: now + serviceGroupsSnapshotTtlMs,
- snapshot,
- });
- snapshot.catch(() => {
- if (serviceGroupsSnapshotCache.get(key)?.snapshot === snapshot) {
- serviceGroupsSnapshotCache.delete(key);
- }
- });
- return snapshot;
-}
-
-function serviceGroupsSnapshotKey(
- document: DashboardDocument,
- context: DatasourceContext,
-): string {
- return JSON.stringify(
- {
- fetchIdentity: context.fetchIdentity,
- prometheusBaseUrl: context.prometheusBaseUrl,
- prometheusRangeSeconds: context.prometheusRangeSeconds,
- prometheusStepSeconds: context.prometheusStepSeconds,
- requestTimeoutMs: context.requestTimeoutMs,
- serviceGroups: document.serviceGroups.map((group) => ({
- id: group.id,
- services: group.services.map((service) => ({
- datasource: service.datasource,
- id: service.id,
- })),
- })),
- },
- );
-}
-
-async function resolveTelemetryCard(
- card: TelemetryCard,
- context: DatasourceContext,
-): Promise {
- if (card.datasource?.type !== "external" || card.datasource.adapter !== "prometheus") {
- return structuredClone(card);
- }
-
- try {
- const [instant, range] = await Promise.all([
- prometheusQuery(card.datasource.reference, context),
- prometheusRangeQuery(card.datasource.reference, context),
- ]);
- const current = pickMaxVectorResult(instant);
- const currentValue = Number(current?.value?.[1]);
- const sparkline = matrixPoints(range);
-
- if (!Number.isFinite(currentValue)) return markStale(card, "no telemetry data");
-
- return {
- ...structuredClone(card),
- value: metricValueWithLiveNumber(card.value, currentValue),
- severity: severityForValue(currentValue, card.thresholds),
- detail: prometheusMetricDetail(current?.metric || {}, card.detail),
- sparkline: sparkline.length ? sparkline : card.sparkline,
- };
- } catch {
- return markStale(card, card.detail || "telemetry unavailable");
- }
-}
-
-async function resolveServiceGroup(
- group: ServiceGroup,
- context: DatasourceContext,
-): Promise {
- return {
- ...structuredClone(group),
- services: await Promise.all(
- group.services.map((service) => resolveService(service, context)),
- ),
- };
-}
-
-async function resolveService(
- service: ServiceEntry,
- context: DatasourceContext,
-): Promise {
- const datasource = service.datasource;
- if (datasource?.type !== "external") return structuredClone(service);
-
- if (datasource.adapter === "http-status") {
- return resolveHttpStatusService(service, datasource.reference, context);
- }
-
- if (datasource.adapter === "prometheus") {
- return resolvePrometheusService(service, datasource.reference, context);
- }
-
- return structuredClone(service);
-}
-
-async function resolveHttpStatusService(
- service: ServiceEntry,
- reference: string,
- context: DatasourceContext,
-): Promise {
- const request = parseHttpStatusReference(reference);
- if (!request) return structuredClone(service);
-
- try {
- const startedAt = Date.now();
- const response = await fetchWithTimeout(
- context.fetch,
- request.url,
- {
- cache: "no-store",
- method: request.method,
- },
- context.requestTimeoutMs,
- );
- const elapsedMs = Math.max(0, Math.round(Date.now() - startedAt));
- const badge = await uptimeBadge(response);
- if (badge) {
- const ping = badge.ping;
- const status = badge.status || "UNKNOWN";
- return {
- ...structuredClone(service),
- severity: uptimeBadgeSeverity(status),
- detail: Number.isFinite(ping)
- ? `${Math.round(ping as number)} ms`
- : status.toLowerCase(),
- };
- }
-
- return {
- ...structuredClone(service),
- severity: response.ok ? "ok" : response.status >= 500 ? "danger" : "warning",
- detail: response.ok ? `${elapsedMs} ms` : `HTTP ${response.status}`,
- };
- } catch {
- return {
- ...structuredClone(service),
- severity: "unavailable",
- detail: "unavailable",
- };
- }
-}
-
-async function resolvePrometheusService(
- service: ServiceEntry,
- reference: string,
- context: DatasourceContext,
-): Promise {
- try {
- const result = pickMaxVectorResult(await prometheusQuery(reference, context));
- const value = Number(result?.value?.[1]);
- const ok = Number.isFinite(value) && value > 0;
-
- return {
- ...structuredClone(service),
- severity: ok ? "ok" : "unavailable",
- detail: ok ? "up" : "down",
- };
- } catch {
- return {
- ...structuredClone(service),
- severity: "unavailable",
- detail: "unavailable",
- };
- }
-}
-
-async function resolveModules(
- modules: DashboardModule[],
- serviceGroups: ServiceGroup[],
- context: DatasourceContext,
-): Promise {
- const resolved = await Promise.all(
- modules.map((module) => resolveModule(module, context)),
- );
- return resolved.map((module) =>
- module.id === "runtime-health-summary"
- ? runtimeHealthSummary(module, serviceGroups)
- : module,
- );
-}
-
-async function resolveModule(
- module: DashboardModule,
- context: DatasourceContext,
-): Promise {
- if (module.datasource?.type !== "external" || module.datasource.adapter !== "weather") {
- return structuredClone(module);
- }
-
- try {
- const weather = await requestJson(
- context.fetch,
- openMeteoUrl(module.datasource.reference),
- context.requestTimeoutMs,
- );
- const current = weather.current;
- const temperature = Number(current?.temperature_2m);
- if (!Number.isFinite(temperature)) {
- return {
- ...structuredClone(module),
- severity: "stale",
- detail: "weather unavailable",
- };
- }
-
- const apparent = Number(current?.apparent_temperature);
- const wind = Number(current?.wind_speed_10m);
- const condition = weatherCondition(Number(current?.weather_code));
-
- return {
- ...structuredClone(module),
- value: `${temperature.toFixed(1)} C`,
- detail: [
- condition,
- Number.isFinite(apparent) ? `feels ${apparent.toFixed(1)} C` : "",
- Number.isFinite(wind) ? `wind ${Math.round(wind)} km/h` : "",
- ].filter(Boolean).join(" - "),
- severity: "ok",
- };
- } catch {
- return {
- ...structuredClone(module),
- severity: "stale",
- detail: "weather unavailable",
- };
- }
-}
-
-function runtimeHealthSummary(
- module: DashboardModule,
- serviceGroups: ServiceGroup[],
-): DashboardModule {
- const services = serviceGroups.flatMap((group) => group.services);
- const down = services.filter((service) =>
- service.severity === "danger" || service.severity === "unavailable"
- ).length;
- const warning = services.filter((service) => service.severity === "warning").length;
- const ok = services.filter((service) => service.severity === "ok").length;
-
- if (down > 0) {
- return {
- ...structuredClone(module),
- value: `${down} service${down === 1 ? "" : "s"} down`,
- detail: `${warning} warning${warning === 1 ? "" : "s"} - ${ok} service${ok === 1 ? "" : "s"} ok`,
- severity: "danger",
- };
- }
-
- if (warning > 0) {
- return {
- ...structuredClone(module),
- value: `${warning} service${warning === 1 ? "" : "s"} warning`,
- detail: `${ok} service${ok === 1 ? "" : "s"} ok`,
- severity: "warning",
- };
- }
-
- return {
- ...structuredClone(module),
- value: "all systems operational",
- detail: `${ok} service${ok === 1 ? "" : "s"} ok`,
- severity: "ok",
- };
-}
-
-async function resolveStatusStrips(
- strips: StatusStrip[],
- refreshIntervalSeconds: number | undefined,
- serviceGroups: ServiceGroup[],
- context: DatasourceContext,
-): Promise {
- const [uptime, loadAverage] = await Promise.all([
- prometheusScalar(
- 'time() - node_boot_time_seconds{job="node",host="linux-infra"}',
- context,
- ).catch(() => null),
- prometheusLoadAverage(context).catch(() => null),
- ]);
- const health = serviceHealthSummary(serviceGroups);
-
- return strips.map((strip) => ({
- ...structuredClone(strip),
- items: strip.items.map((item) =>
- resolveStatusItem(item, {
- health,
- loadAverage,
- refreshIntervalSeconds,
- uptime,
- }),
- ),
- }));
-}
-
-function resolveStatusItem(
- item: StatusItem,
- values: {
- health: { severity: Severity; value: string };
- loadAverage: string | null;
- refreshIntervalSeconds?: number;
- uptime: number | null;
- },
-): StatusItem {
- if (item.id === "system-status") {
- return {
- ...structuredClone(item),
- value: values.health.value,
- severity: values.health.severity,
- };
- }
-
- if (item.id === "last-sync") {
- return {
- ...structuredClone(item),
- value: "just now",
- severity: "ok",
- };
- }
-
- if (item.id === "uptime" && values.uptime !== null) {
- return {
- ...structuredClone(item),
- value: formatDuration(values.uptime),
- severity: "ok",
- };
- }
-
- if (item.id === "load-avg" && values.loadAverage) {
- return {
- ...structuredClone(item),
- value: values.loadAverage,
- severity: "neutral",
- };
- }
-
- if (item.id === "auto-refresh" && values.refreshIntervalSeconds) {
- return {
- ...structuredClone(item),
- value: `${values.refreshIntervalSeconds}s`,
- severity: "neutral",
- };
- }
-
- return structuredClone(item);
-}
-
-async function resolveStatusTile(
- item: StatusItem,
- refreshIntervalSeconds: number | undefined,
- document: DashboardDocument,
- context: DatasourceContext,
-): Promise {
- if (item.id === "system-status") {
- const resolvedGroups = await serviceGroupsSnapshot(document, context);
- const health = serviceHealthSummary(resolvedGroups);
- return {
- ...structuredClone(item),
- value: health.value,
- severity: health.severity,
- };
- }
-
- if (item.id === "last-sync") {
- return {
- ...structuredClone(item),
- value: "just now",
- severity: "ok",
- };
- }
-
- if (item.id === "uptime") {
- const uptime = await prometheusScalar(
- 'time() - node_boot_time_seconds{job="node",host="linux-infra"}',
- context,
- ).catch(() => null);
- return uptime === null
- ? structuredClone(item)
- : {
- ...structuredClone(item),
- value: formatDuration(uptime),
- severity: "ok",
- };
- }
-
- if (item.id === "load-avg") {
- const loadAverage = await prometheusLoadAverage(context).catch(() => null);
- return loadAverage
- ? {
- ...structuredClone(item),
- value: loadAverage,
- severity: "neutral",
- }
- : structuredClone(item);
- }
-
- if (item.id === "auto-refresh" && refreshIntervalSeconds) {
- return {
- ...structuredClone(item),
- value: `${refreshIntervalSeconds}s`,
- severity: "neutral",
- };
- }
-
- return structuredClone(item);
-}
-
-function serviceHealthSummary(serviceGroups: ServiceGroup[]): {
- severity: Severity;
- value: string;
-} {
- const services = serviceGroups.flatMap((group) => group.services);
- const down = services.filter((service) =>
- service.severity === "danger" || service.severity === "unavailable"
- ).length;
- const warning = services.filter((service) => service.severity === "warning").length;
-
- if (down > 0) {
- return {
- severity: "danger",
- value: `${down} service${down === 1 ? "" : "s"} down`,
- };
- }
-
- if (warning > 0) {
- return {
- severity: "warning",
- value: `${warning} service${warning === 1 ? "" : "s"} warning`,
- };
- }
-
- return {
- severity: "ok",
- value: "All systems operational",
- };
-}
-
-async function prometheusQuery(
- query: string,
- context: DatasourceContext,
-): Promise {
- const url = prometheusApiUrl(context.prometheusBaseUrl, "/api/v1/query", {
- query,
- });
- const payload = await requestJson>(
- context.fetch,
- url,
- context.requestTimeoutMs,
- );
- return payload.status === "success" ? payload.data.result || [] : [];
-}
-
-async function prometheusRangeQuery(
- query: string,
- context: DatasourceContext,
-): Promise {
- const end = Math.floor(Date.now() / 1000);
- const start = end - context.prometheusRangeSeconds;
- const url = prometheusApiUrl(context.prometheusBaseUrl, "/api/v1/query_range", {
- query,
- start: String(start),
- end: String(end),
- step: String(context.prometheusStepSeconds),
- });
- const payload = await requestJson>(
- context.fetch,
- url,
- context.requestTimeoutMs,
- );
- return payload.status === "success" ? payload.data.result || [] : [];
-}
-
-async function prometheusScalar(
- query: string,
- context: DatasourceContext,
-): Promise {
- const result = pickMaxVectorResult(await prometheusQuery(query, context));
- const value = Number(result?.value?.[1]);
- return Number.isFinite(value) ? value : null;
-}
-
-async function prometheusLoadAverage(context: DatasourceContext): Promise {
- const [one, five, fifteen] = await Promise.all([
- prometheusScalar('node_load1{job="node",host="linux-infra"}', context),
- prometheusScalar('node_load5{job="node",host="linux-infra"}', context),
- prometheusScalar('node_load15{job="node",host="linux-infra"}', context),
- ]);
-
- if (one === null || five === null || fifteen === null) return null;
- return [one, five, fifteen].map((value) => value.toFixed(2)).join(" ");
-}
-
-async function requestJson(
- fetch: DatasourceFetch,
- url: string,
- timeoutMs: number,
-): Promise {
- const response = await fetchWithTimeout(
- fetch,
- url,
- { cache: "no-store" },
- timeoutMs,
- );
- if (!response.ok) throw new Error(`request failed: ${response.status}`);
- return response.json() as Promise;
-}
-
-async function fetchWithTimeout(
- fetch: DatasourceFetch,
- url: string,
- init: RequestInit,
- timeoutMs: number,
-): Promise {
- const controller = new AbortController();
- const timeout = setTimeout(() => controller.abort(), timeoutMs);
- try {
- return await fetch(url, {
- ...init,
- signal: controller.signal,
- });
- } finally {
- clearTimeout(timeout);
- }
-}
-
-function prometheusApiUrl(
- baseUrl: string,
- pathname: string,
- params: Record,
-): string {
- const url = new URL(pathname, ensureTrailingSlash(baseUrl));
- Object.entries(params).forEach(([key, value]) => url.searchParams.set(key, value));
- return url.toString();
-}
-
-function ensureTrailingSlash(value: string): string {
- return value.endsWith("/") ? value : `${value}/`;
-}
-
-function pickMaxVectorResult(
- results: PrometheusVectorResult[],
-): PrometheusVectorResult | null {
- return results.reduce((winner, item) => {
- if (!winner) return item;
- return Number(item.value?.[1]) > Number(winner.value?.[1]) ? item : winner;
- }, null);
-}
-
-function matrixPoints(results: PrometheusMatrixResult[]): number[] {
- const byTimestamp = new Map();
-
- for (const series of results) {
- for (const [timestamp, value] of series.values || []) {
- const numeric = Number(value);
- if (!Number.isFinite(numeric)) continue;
- const previous = byTimestamp.get(timestamp);
- if (previous === undefined || numeric > previous) {
- byTimestamp.set(timestamp, numeric);
- }
- }
- }
-
- return [...byTimestamp.entries()]
- .sort(([left], [right]) => left - right)
- .map(([, value]) => value);
-}
-
-function metricValueWithLiveNumber(value: MetricValue, nextValue: number): MetricValue {
- if (value.kind === "text") return value;
- return {
- ...value,
- value: value.kind === "percent" ? clamp(nextValue, 0, 100) : Math.max(0, nextValue),
- };
-}
-
-function severityForValue(
- value: number,
- thresholds: TelemetryCard["thresholds"],
-): Severity {
- if (thresholds?.danger !== undefined && value >= thresholds.danger) return "danger";
- if (thresholds?.warning !== undefined && value >= thresholds.warning) return "warning";
- return "ok";
-}
-
-function prometheusMetricDetail(
- metric: Record,
- fallback = "",
-): string {
- const host = metric.host || metric.instance || metric.job;
- const detail = metric.mountpoint || metric.name || metric.container || metric.id;
- const parts = [host, detail].filter(Boolean);
- if (parts.length) return parts.join(" ");
- return fallback.replace(/^fallback\s*-\s*/i, "") || "telemetry";
-}
-
-function markStale(card: TelemetryCard, detail: string): TelemetryCard {
- return {
- ...structuredClone(card),
- severity: "stale",
- detail,
- };
-}
-
-function parseHttpStatusReference(reference: string): { method: string; url: string } | null {
- const match = /^(GET|HEAD|POST)\s+(.+)$/i.exec(reference.trim());
- if (!match) return null;
- return { method: match[1].toUpperCase(), url: match[2] };
-}
-
-interface PrometheusResponse {
- status: string;
- data: {
- result?: T[];
- };
-}
-
-interface OpenMeteoResponse {
- current?: {
- apparent_temperature?: number;
- temperature_2m?: number;
- weather_code?: number;
- wind_speed_10m?: number;
- };
-}
-
-interface UptimeBadgeResponse {
- status?: string;
- ping?: number;
-}
-
-async function uptimeBadge(response: Response): Promise {
- const contentType = response.headers.get("content-type") || "";
- if (!contentType.includes("json")) return null;
-
- try {
- const payload = await response.clone().json() as UptimeBadgeResponse;
- return typeof payload.status === "string" ? payload : null;
- } catch {
- return null;
- }
-}
-
-function uptimeBadgeSeverity(status = ""): Severity {
- const normalized = status.toUpperCase();
- if (normalized === "UP") return "ok";
- if (normalized === "PENDING" || normalized === "MAINTENANCE") return "warning";
- return "unavailable";
-}
-
-function openMeteoUrl(reference: string): string {
- const params = new URLSearchParams();
- const serialized = reference.startsWith("open-meteo:")
- ? reference.slice("open-meteo:".length)
- : reference;
-
- new URLSearchParams(serialized).forEach((value, key) => {
- params.set(key, value);
- });
- params.set(
- "current",
- "temperature_2m,apparent_temperature,weather_code,wind_speed_10m",
- );
- params.set("timezone", "Europe/Amsterdam");
-
- return `https://api.open-meteo.com/v1/forecast?${params.toString()}`;
-}
-
-function weatherCondition(code: number): string {
- if (code === 0) return "Clear";
- if ([1, 2].includes(code)) return "Partly cloudy";
- if (code === 3) return "Cloudy";
- if ([45, 48].includes(code)) return "Fog";
- if (code >= 51 && code <= 67) return "Rain";
- if (code >= 71 && code <= 77) return "Snow";
- if (code >= 80 && code <= 82) return "Showers";
- if (code >= 95) return "Thunderstorm";
- return "Mixed";
-}
-
-function clamp(value: number, min: number, max: number): number {
- return Math.max(min, Math.min(max, value));
-}
-
-function formatDuration(totalSeconds: number): string {
- const seconds = Math.max(0, Math.floor(totalSeconds));
- const days = Math.floor(seconds / 86_400);
- const hours = Math.floor((seconds % 86_400) / 3_600);
- const minutes = Math.floor((seconds % 3_600) / 60);
- return `${days}d ${hours}h ${minutes}m`;
-}
-
-function missingTile(tile: DashboardTileReference): DashboardTileResolution {
- return {
- state: "not_found",
- tile,
- message: `Dashboard tile not found: ${tileKey(tile)}`,
- };
-}
-
-function tileKey(tile: DashboardTileReference): string {
- if (tile.kind === "status") return `${tile.kind}:${tile.stripId}:${tile.id}`;
- if (tile.kind === "service") return `${tile.kind}:${tile.groupId}:${tile.id}`;
- return `${tile.kind}:${tile.id}`;
-}
diff --git a/apps/web/src/lib/testing/external-api-mocks.test.ts b/apps/web/src/lib/testing/external-api-mocks.test.ts
deleted file mode 100644
index 7d487ae..0000000
--- a/apps/web/src/lib/testing/external-api-mocks.test.ts
+++ /dev/null
@@ -1,55 +0,0 @@
-import { setupServer } from "msw/node";
-import { afterAll, afterEach, beforeAll, describe, expect, test } from "vitest";
-import { externalApiHandlers } from "./external-api-mocks";
-
-const server = setupServer(...externalApiHandlers);
-
-describe("external API mocks", () => {
- beforeAll(() => {
- server.listen({ onUnhandledRequest: "error" });
- });
-
- afterEach(() => {
- server.resetHandlers();
- });
-
- afterAll(() => {
- server.close();
- });
-
- test("defines deterministic handlers for deferred datasource adapters", () => {
- expect(externalApiHandlers).toHaveLength(3);
- expect(externalApiHandlers.map((handler) => handler.info.header)).toEqual([
- "GET https://prometheus.dimensionlab.net/api/v1/query",
- "GET https://uptime.dimensionlab.net/api/status-page/*",
- "GET https://api.open-meteo.com/v1/forecast",
- ]);
- });
-
- test("intercepts deferred datasource requests without live services", async () => {
- const prometheus = await fetch(
- "https://prometheus.dimensionlab.net/api/v1/query",
- ).then((response) => response.json());
- const status = await fetch(
- "https://uptime.dimensionlab.net/api/status-page/dimensionlab",
- ).then((response) => response.json());
- const weather = await fetch(
- "https://api.open-meteo.com/v1/forecast?latitude=52.37&longitude=4.9",
- ).then((response) => response.json());
-
- expect(prometheus).toMatchObject({
- status: "success",
- data: { result: [{ value: [1771430400, "1"] }] },
- });
- expect(status).toMatchObject({
- status: "ok",
- incidents: [],
- });
- expect(weather).toMatchObject({
- current: {
- temperature_2m: 21.4,
- weather_code: 0,
- },
- });
- });
-});
diff --git a/apps/web/src/lib/testing/external-api-mocks.ts b/apps/web/src/lib/testing/external-api-mocks.ts
deleted file mode 100644
index c8f6399..0000000
--- a/apps/web/src/lib/testing/external-api-mocks.ts
+++ /dev/null
@@ -1,33 +0,0 @@
-import { http, HttpResponse } from "msw";
-
-export const externalApiHandlers = [
- http.get("https://prometheus.dimensionlab.net/api/v1/query", () =>
- HttpResponse.json({
- status: "success",
- data: {
- resultType: "vector",
- result: [
- {
- metric: { instance: "fixture" },
- value: [1771430400, "1"],
- },
- ],
- },
- }),
- ),
- http.get("https://uptime.dimensionlab.net/api/status-page/*", () =>
- HttpResponse.json({
- status: "ok",
- incidents: [],
- monitors: [{ name: "fixture", status: "up" }],
- }),
- ),
- http.get("https://api.open-meteo.com/v1/forecast", () =>
- HttpResponse.json({
- current: {
- temperature_2m: 21.4,
- weather_code: 0,
- },
- }),
- ),
-];
diff --git a/apps/web/src/lib/workspace-boundary.test.ts b/apps/web/src/lib/workspace-boundary.test.ts
deleted file mode 100644
index 58cf203..0000000
--- a/apps/web/src/lib/workspace-boundary.test.ts
+++ /dev/null
@@ -1,578 +0,0 @@
-import { spawnSync } from "node:child_process";
-import { chmodSync, existsSync, mkdtempSync, readFileSync, writeFileSync } from "node:fs";
-import { tmpdir } from "node:os";
-import { join } from "node:path";
-import { describe, expect, test } from "vitest";
-
-const root = existsSync(join(process.cwd(), "turbo.json"))
- ? process.cwd()
- : join(process.cwd(), "..", "..");
-
-describe("workspace boundaries", () => {
- test("declares the root as a turbo-managed bun workspace", () => {
- const packageJson = JSON.parse(
- readFileSync(join(root, "package.json"), "utf8"),
- ) as {
- private?: boolean;
- scripts?: Record;
- workspaces?: string[];
- };
-
- expect(packageJson.private).toBe(true);
- expect(packageJson.workspaces).toEqual(["apps/*", "packages/*"]);
- expect(packageJson.scripts?.build).toBe("turbo run build");
- expect(existsSync(join(root, "turbo.json"))).toBe(true);
- });
-
- test("keeps release orchestration in the root turbo task graph", () => {
- const rootPackage = JSON.parse(
- readFileSync(join(root, "package.json"), "utf8"),
- ) as {
- scripts?: Record;
- };
- const webPackage = JSON.parse(
- readFileSync(join(root, "apps/web/package.json"), "utf8"),
- ) as { scripts?: Record };
- const turboConfig = JSON.parse(
- readFileSync(join(root, "turbo.json"), "utf8"),
- ) as {
- globalDependencies?: string[];
- tasks?: Record;
- };
-
- 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;
- };
-
- 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;
- };
-
- for (const taskName of ["build", "build-storybook"]) {
- expect(turboConfig.tasks?.[taskName]?.inputs).toEqual([
- "$TURBO_DEFAULT$",
- ".env*",
- ]);
- }
- });
-
- test("keeps the website app and reusable UI library as separate packages", () => {
- const webPackage = JSON.parse(
- readFileSync(join(root, "apps/web/package.json"), "utf8"),
- ) as { dependencies?: Record; name?: string };
- const uiPackage = JSON.parse(
- readFileSync(join(root, "packages/ui/package.json"), "utf8"),
- ) as { exports?: Record; name?: string };
-
- expect(webPackage.name).toBe("@dimensionlab/web");
- expect(webPackage.dependencies?.["@dimensionlab/ui"]).toBe("workspace:*");
- expect(uiPackage.name).toBe("@dimensionlab/ui");
- expect(uiPackage.exports).toHaveProperty(".");
- expect(uiPackage.exports).toHaveProperty("./styles.css");
- });
-
- test("keeps the dashboard model in a reusable internal package", () => {
- const webPackage = JSON.parse(
- readFileSync(join(root, "apps/web/package.json"), "utf8"),
- ) as { dependencies?: Record };
- const modelPackage = JSON.parse(
- readFileSync(join(root, "packages/dashboard-model/package.json"), "utf8"),
- ) as { exports?: Record; 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 } };
- const modelPackage = JSON.parse(
- readFileSync(join(root, "packages/dashboard-model/package.json"), "utf8"),
- ) as { exports?: Record };
- const uiPackage = JSON.parse(
- readFileSync(join(root, "packages/ui/package.json"), "utf8"),
- ) as { exports?: Record };
- const viteConfig = readFileSync(join(root, "apps/web/vite.config.ts"), "utf8");
- const turboConfig = JSON.parse(
- readFileSync(join(root, "turbo.json"), "utf8"),
- ) as { tasks?: Record };
-
- 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 };
- 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,
-): void {
- expect(actual).toMatchObject(expected);
-}
-
-function runDeployScriptWithFakes(
- commands: Record,
- env: Record,
-): { 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
-`;
-}
diff --git a/apps/web/src/main.tsx b/apps/web/src/main.tsx
deleted file mode 100644
index 9bbebc4..0000000
--- a/apps/web/src/main.tsx
+++ /dev/null
@@ -1,13 +0,0 @@
-import { StrictMode } from "react";
-import { createRoot } from "react-dom/client";
-import App from "./App";
-import "./app.css";
-
-const root = document.getElementById("root");
-if (!root) throw new Error("Missing React root element");
-
-createRoot(root).render(
-
-
- ,
-);
diff --git a/apps/web/src/page.test.tsx b/apps/web/src/page.test.tsx
deleted file mode 100644
index 2ec5869..0000000
--- a/apps/web/src/page.test.tsx
+++ /dev/null
@@ -1,97 +0,0 @@
-import { renderToString } from "react-dom/server";
-import { describe, expect, test } from "vitest";
-import { genericDashboardFixture } from "@dimensionlab/dashboard-model/fixtures";
-import { AppStateView, resolveDocumentMetadata } from "./App";
-
-describe("home page model renderer", () => {
- test("renders the active dashboard model from the runtime state", () => {
- const body = renderToString(
- ,
- );
-
- expect(body).toContain("Operations Console");
- expect(body).toContain("Service Uptime");
- expect(body).toContain("Identity");
- expect(body).toContain("data-model-id=\"service-uptime\"");
- expect(body).not.toContain("primary");
- expect(body).not.toContain("secondary");
- });
-
- test("renders invalid model state without crashing", () => {
- const body = renderToString(
- ,
- );
-
- expect(body).toContain("Invalid Dashboard");
- expect(body).toContain("Validation failed");
- expect(body).toContain("Dashboard document is invalid.");
- expect(body).toContain("/metadata/title is required");
- });
-
- test("derives browser metadata from ready and fallback runtime states", () => {
- const ready = resolveDocumentMetadata({
- state: "ready",
- document: genericDashboardFixture,
- schemaVersion: "dashboard.v1",
- currentRevisionId: "revision-1234567890",
- });
- const empty = resolveDocumentMetadata({
- state: "empty",
- title: "No Dashboard Model",
- subtitle: "No active document",
- message: "No validated dashboard document is active yet.",
- });
-
- expect(ready).toEqual({
- title: "Operations Console",
- description: "Generic environment",
- });
- expect(empty).toEqual({
- title: "No Dashboard Model",
- description: "No active document",
- });
- });
-
- test("renders empty and loading model states without crashing", () => {
- const empty = renderToString(
- ,
- );
- const loading = renderToString(
- ,
- );
-
- expect(empty).toContain("No Dashboard Model");
- expect(empty).toContain("No active document");
- expect(loading).toContain("Loading Dashboard");
- expect(loading).toContain("Fetching active model");
- });
-});
diff --git a/apps/web/src/server/dev.test.ts b/apps/web/src/server/dev.test.ts
deleted file mode 100644
index 60031db..0000000
--- a/apps/web/src/server/dev.test.ts
+++ /dev/null
@@ -1,33 +0,0 @@
-import { readFileSync } from "node:fs";
-import { join } from "node:path";
-import { describe, expect, test } from "vitest";
-import { createDevServerConfig } from "../../vite.config";
-import { apiServerArgs } from "./dev";
-
-describe("local development runtime", () => {
- test("starts the Bun API server together with the Vite dev server", () => {
- const packageJson = JSON.parse(
- readFileSync(join(process.cwd(), "package.json"), "utf8"),
- ) as { scripts?: Record };
-
- expect(packageJson.scripts?.dev).toBe("bun src/server/dev.ts");
- });
-
- test("proxies dashboard API requests from Vite to the Bun API server", () => {
- const server = createDevServerConfig({
- DASHBOARD_DEV_API_TARGET: "http://127.0.0.1:5174",
- });
-
- expect(server?.proxy?.["/api"]).toMatchObject({
- target: "http://127.0.0.1:5174",
- changeOrigin: true,
- });
- });
-
- test("uses development package export conditions for the Bun API server", () => {
- expect(apiServerArgs).toEqual([
- "--conditions=development",
- "src/server/index.ts",
- ]);
- });
-});
diff --git a/apps/web/src/server/dev.ts b/apps/web/src/server/dev.ts
deleted file mode 100644
index fa4dc2f..0000000
--- a/apps/web/src/server/dev.ts
+++ /dev/null
@@ -1,76 +0,0 @@
-const webHost = process.env.HOST || "0.0.0.0";
-const webPort = process.env.PORT || "5173";
-const apiHost = process.env.DASHBOARD_DEV_API_HOST || "127.0.0.1";
-const apiPort = process.env.DASHBOARD_DEV_API_PORT || "5174";
-const apiTarget = `http://${apiHost}:${apiPort}`;
-export const apiServerArgs = [
- "--conditions=development",
- "src/server/index.ts",
-] as const;
-
-if (import.meta.main) {
- runDevServers();
-}
-
-export function runDevServers(): void {
- const children: Array> = [];
- let shuttingDown = false;
-
- function spawn(
- label: string,
- command: string[],
- env: Record = {},
- ): void {
- const child = Bun.spawn(command, {
- env: {
- ...process.env,
- ...env,
- },
- stdin: "inherit",
- stdout: "inherit",
- stderr: "inherit",
- });
- children.push(child);
-
- void child.exited.then((code) => {
- if (shuttingDown) return;
- console.error(`${label} exited with status ${code}`);
- shutdown(code || 1);
- });
- }
-
- function shutdown(code = 0): void {
- if (shuttingDown) return;
- shuttingDown = true;
-
- for (const child of children) {
- child.kill();
- }
-
- void Promise.allSettled(children.map((child) => child.exited)).then(() => {
- process.exit(code);
- });
- }
-
- process.on("SIGINT", () => shutdown(0));
- process.on("SIGTERM", () => shutdown(0));
-
- spawn("api server", [process.execPath, ...apiServerArgs], {
- HOST: apiHost,
- PORT: apiPort,
- });
-
- spawn("vite dev server", [
- process.execPath,
- "x",
- "vite",
- "--host",
- webHost,
- "--port",
- webPort,
- ], {
- DASHBOARD_DEV_API_TARGET: apiTarget,
- });
-
- console.info(`Dashboard API proxy target: ${apiTarget}`);
-}
diff --git a/apps/web/src/server/index.test.ts b/apps/web/src/server/index.test.ts
deleted file mode 100644
index af1035d..0000000
--- a/apps/web/src/server/index.test.ts
+++ /dev/null
@@ -1,77 +0,0 @@
-import { afterAll, describe, expect, test, vi } from "vitest";
-import { handleRequest } from "./index";
-
-describe("server request routing", () => {
- const consoleInfo = vi.spyOn(console, "info").mockImplementation(() => undefined);
-
- afterAll(() => {
- consoleInfo.mockRestore();
- });
-
- test("routes dashboard tile batch requests", async () => {
- const response = await handleRequest(
- new Request("https://example.test/api/dashboard/tiles", {
- method: "POST",
- body: JSON.stringify({
- tiles: [
- {
- kind: "status",
- stripId: "footer-status",
- id: "auto-refresh",
- },
- ],
- }),
- }),
- );
-
- expect(response.status).toBe(200);
- await expect(response.json()).resolves.toMatchObject({
- state: "ready",
- tiles: [
- {
- state: "ready",
- tile: {
- kind: "status",
- stripId: "footer-status",
- id: "auto-refresh",
- },
- },
- ],
- });
- });
-
- test("rejects non-post dashboard tile batch requests", async () => {
- const response = await handleRequest(
- new Request("https://example.test/api/dashboard/tiles", {
- method: "GET",
- }),
- );
-
- expect(response.status).toBe(405);
- expect(response.headers.get("allow")).toBe("POST");
- });
-
- test("routes dashboard event stream requests", async () => {
- const response = await handleRequest(
- new Request("https://example.test/api/dashboard/events", {
- method: "GET",
- }),
- );
-
- expect(response.status).toBe(200);
- expect(response.headers.get("content-type")).toBe(
- "text/event-stream; charset=utf-8",
- );
- });
-
- test("rejects non-get dashboard event stream requests", async () => {
- const response = await handleRequest(
- new Request("https://example.test/api/dashboard/events", {
- method: "POST",
- }),
- );
-
- expect(response.status).toBe(405);
- expect(response.headers.get("allow")).toBe("GET");
- });
-});
diff --git a/apps/web/src/server/index.ts b/apps/web/src/server/index.ts
deleted file mode 100644
index 9cbcbd9..0000000
--- a/apps/web/src/server/index.ts
+++ /dev/null
@@ -1,104 +0,0 @@
-import { extname, normalize } from "node:path";
-import { handleAgentDashboardRoute } from "./routes/agent-dashboard";
-import {
- handleDashboardEventsRoute,
- handleDashboardRoute,
- handleDashboardTileRoute,
- handleDashboardTilesRoute,
-} from "./routes/dashboard";
-
-const host = process.env.HOST || "0.0.0.0";
-const port = Number(process.env.PORT || 3000);
-const distRoot = `${process.cwd()}/dist`;
-
-const contentTypes = new Map([
- [".css", "text/css; charset=utf-8"],
- [".html", "text/html; charset=utf-8"],
- [".js", "text/javascript; charset=utf-8"],
- [".json", "application/json; charset=utf-8"],
- [".svg", "image/svg+xml"],
- [".wasm", "application/wasm"],
-]);
-
-export async function handleRequest(request: Request): Promise {
- const url = new URL(request.url);
-
- if (url.pathname === "/api/dashboard") {
- if (request.method !== "GET") return methodNotAllowed(["GET"]);
- return handleDashboardRoute();
- }
-
- if (url.pathname === "/api/dashboard/tiles") {
- if (request.method !== "POST") return methodNotAllowed(["POST"]);
- return handleDashboardTilesRoute(request);
- }
-
- if (url.pathname === "/api/dashboard/events") {
- if (request.method !== "GET") return methodNotAllowed(["GET"]);
- return handleDashboardEventsRoute({ signal: request.signal });
- }
-
- if (url.pathname.startsWith("/api/dashboard/tile/")) {
- if (request.method !== "GET") return methodNotAllowed(["GET"]);
- return handleDashboardTileRoute(url.pathname);
- }
-
- if (url.pathname === "/api/agent/dashboard") {
- if (request.method !== "POST") return methodNotAllowed(["POST"]);
- return handleAgentDashboardRoute(request);
- }
-
- if (url.pathname.startsWith("/api/")) {
- return Response.json({ ok: false, message: "Not found" }, { status: 404 });
- }
-
- return serveStaticAsset(url.pathname);
-}
-
-async function serveStaticAsset(pathname: string): Promise {
- const safePath = normalize(pathname).replace(/^(\.\.(\/|\\|$))+/, "");
- const assetPath = safePath === "/" || safePath === "." ? "/index.html" : safePath;
- const file = Bun.file(`${distRoot}${assetPath}`);
-
- if (await file.exists()) {
- return new Response(file, {
- headers: contentTypeHeaders(assetPath),
- });
- }
-
- const fallback = Bun.file(`${distRoot}/index.html`);
- if (await fallback.exists()) {
- return new Response(fallback, {
- headers: contentTypeHeaders(".html"),
- });
- }
-
- return new Response("Build output not found", { status: 404 });
-}
-
-function methodNotAllowed(allowedMethods: string[]): Response {
- return Response.json(
- { ok: false, message: "Method not allowed" },
- {
- status: 405,
- headers: {
- Allow: allowedMethods.join(", "),
- },
- },
- );
-}
-
-function contentTypeHeaders(pathname: string): HeadersInit {
- const contentType = contentTypes.get(extname(pathname));
- return contentType ? { "Content-Type": contentType } : {};
-}
-
-if (import.meta.main) {
- Bun.serve({
- hostname: host,
- port,
- fetch: handleRequest,
- });
-
- console.info(`Dimension Lab website listening on http://${host}:${port}`);
-}
diff --git a/apps/web/src/server/routes/agent-dashboard.test.ts b/apps/web/src/server/routes/agent-dashboard.test.ts
deleted file mode 100644
index a3a6022..0000000
--- a/apps/web/src/server/routes/agent-dashboard.test.ts
+++ /dev/null
@@ -1,12 +0,0 @@
-import { describe, expect, test } from "vitest";
-import { handleAgentDashboardRoute } from "./agent-dashboard";
-
-describe("agent dashboard API route", () => {
- test("delegates unauthorized requests to the existing agent handler", async () => {
- const response = await handleAgentDashboardRoute(
- new Request("http://localhost/api/agent/dashboard", { method: "POST" }),
- );
-
- expect(response.status).toBe(401);
- });
-});
diff --git a/apps/web/src/server/routes/agent-dashboard.ts b/apps/web/src/server/routes/agent-dashboard.ts
deleted file mode 100644
index 1ce77da..0000000
--- a/apps/web/src/server/routes/agent-dashboard.ts
+++ /dev/null
@@ -1,5 +0,0 @@
-import { handleAgentDashboardRequest } from "$lib/server/agent-config";
-
-export function handleAgentDashboardRoute(request: Request): Promise {
- return handleAgentDashboardRequest(request);
-}
diff --git a/apps/web/src/server/routes/dashboard.test.ts b/apps/web/src/server/routes/dashboard.test.ts
deleted file mode 100644
index a4cf7ef..0000000
--- a/apps/web/src/server/routes/dashboard.test.ts
+++ /dev/null
@@ -1,799 +0,0 @@
-import { afterAll, afterEach, describe, expect, test, vi } from "vitest";
-import { dimensionLabDashboardFixture } from "$lib/dashboard-seed/dimensionlab";
-import {
- createDashboardTileCache,
- dashboardTileCacheKey,
- handleDashboardEventsRoute,
- handleDashboardTilesRoute,
- handleDashboardTileRoute,
- loadDashboardResponse,
- loadDashboardTilesResponse,
- loadDashboardTileResponse,
- type DashboardTileResolutionLogEvent,
-} from "./dashboard";
-
-describe("dashboard API route", () => {
- const consoleInfo = vi.spyOn(console, "info").mockImplementation(() => undefined);
-
- afterEach(() => {
- consoleInfo.mockClear();
- });
-
- afterAll(() => {
- consoleInfo.mockRestore();
- });
-
- test("returns the ready dashboard shell without hydrating live datasources", async () => {
- const fetch = vi.spyOn(globalThis, "fetch").mockRejectedValue(
- new Error("live datasource fetch should not run for the shell response"),
- );
-
- const response = await loadDashboardResponse({
- refreshSeedDocument: true,
- seedIfEmpty: true,
- });
-
- expect(response.state).toBe("ready");
- if (response.state !== "ready") throw new Error("expected ready dashboard");
- expect(response.document.metadata.title).toBe(
- dimensionLabDashboardFixture.metadata.title,
- );
- expect(fetch).not.toHaveBeenCalled();
-
- fetch.mockRestore();
- });
-
- test("reports when client-side live hydration is disabled", async () => {
- const previous = process.env.DISABLE_LIVE_DATASOURCES;
- process.env.DISABLE_LIVE_DATASOURCES = "1";
-
- try {
- const response = await loadDashboardResponse({
- refreshSeedDocument: true,
- seedIfEmpty: true,
- });
-
- expect(response.state).toBe("ready");
- if (response.state !== "ready") throw new Error("expected ready dashboard");
- expect(response.liveDatasourceHydration).toEqual({ enabled: false });
- } finally {
- if (previous === undefined) {
- delete process.env.DISABLE_LIVE_DATASOURCES;
- } else {
- process.env.DISABLE_LIVE_DATASOURCES = previous;
- }
- }
- });
-
- test("hydrates a telemetry tile independently from the dashboard shell", async () => {
- const fetch = vi.fn(async (input: RequestInfo | URL) => {
- const url = String(input);
-
- if (url.startsWith("https://prometheus.example/api/v1/query_range")) {
- return jsonResponse({
- status: "success",
- data: {
- result: [
- {
- metric: { host: "linux-infra" },
- values: [
- [1771430000, "10"],
- [1771430060, "20"],
- [1771430120, "42"],
- ],
- },
- ],
- },
- });
- }
-
- if (url.startsWith("https://prometheus.example/api/v1/query")) {
- return jsonResponse({
- status: "success",
- data: {
- result: [
- {
- metric: { host: "linux-infra" },
- value: [1771430400, "42"],
- },
- ],
- },
- });
- }
-
- throw new Error(`Unhandled test request: ${url}`);
- });
-
- const response = await loadDashboardTileResponse(
- { kind: "telemetry", id: "infra-ram" },
- {
- fetch,
- prometheusBaseUrl: "https://prometheus.example",
- refreshSeedDocument: true,
- seedIfEmpty: true,
- },
- );
-
- expect(response.state).toBe("ready");
- if (response.state !== "ready") throw new Error("expected ready tile");
- expect(response.tile).toEqual({ kind: "telemetry", id: "infra-ram" });
- expect(response.item).toMatchObject({
- id: "infra-ram",
- value: { kind: "percent", value: 42 },
- severity: "ok",
- detail: "linux-infra",
- sparkline: [10, 20, 42],
- });
- expect(fetch).toHaveBeenCalledWith(
- expect.stringContaining("/api/v1/query?"),
- expect.objectContaining({ cache: "no-store" }),
- );
- expect(fetch).toHaveBeenCalledWith(
- expect.stringContaining("/api/v1/query_range?"),
- expect.objectContaining({ cache: "no-store" }),
- );
- });
-
- test("hydrates a service tile with its service group identity", async () => {
- const fetch = vi.fn(async () =>
- jsonResponse({
- status: "UP",
- ping: 42,
- }),
- );
-
- const response = await loadDashboardTileResponse(
- { kind: "service", groupId: "essentials", id: "vaultwarden" },
- {
- fetch,
- refreshSeedDocument: true,
- seedIfEmpty: true,
- },
- );
-
- expect(response.state).toBe("ready");
- if (response.state !== "ready") throw new Error("expected ready tile");
- expect(response.tile).toEqual({
- kind: "service",
- groupId: "essentials",
- id: "vaultwarden",
- });
- expect(response.item).toMatchObject({
- id: "vaultwarden",
- severity: "ok",
- detail: "42 ms",
- });
- });
-
- test("caches ready tile responses until the tile ttl expires", async () => {
- const cache = createDashboardTileCache();
- let now = 1_000;
- const fetch = vi.fn(async () => {
- now += 7;
- return jsonResponse({
- status: "UP",
- ping: 42,
- });
- });
- const tile = { kind: "service", groupId: "essentials", id: "vaultwarden" } as const;
-
- const first = await loadDashboardTileResponse(tile, {
- fetch,
- now: () => now,
- refreshSeedDocument: true,
- seedIfEmpty: true,
- tileCache: cache,
- });
- const second = await loadDashboardTileResponse(tile, {
- fetch,
- now: () => now + 29_999,
- refreshSeedDocument: true,
- seedIfEmpty: true,
- tileCache: cache,
- });
- now += 30_001;
- const third = await loadDashboardTileResponse(tile, {
- fetch,
- now: () => now,
- refreshSeedDocument: true,
- seedIfEmpty: true,
- tileCache: cache,
- });
-
- expect(first).toEqual(second);
- expect(third.state).toBe("ready");
- expect(fetch).toHaveBeenCalledTimes(2);
- });
-
- test("logs tile duration and cache hit or miss metadata", async () => {
- let now = 1_000;
- const logs: DashboardTileResolutionLogEvent[] = [];
- const tile = { kind: "service", groupId: "essentials", id: "vaultwarden" } as const;
- const service = dimensionLabDashboardFixture.serviceGroups
- .flatMap((group) => group.services)
- .find((item) => item.id === tile.id);
- if (!service) throw new Error("missing service fixture");
-
- let cacheCalls = 0;
- const tileCache = {
- async resolve() {
- cacheCalls += 1;
- if (cacheCalls === 1) now += 7;
- const cacheState = cacheCalls === 1 ? "miss" as const : "hit" as const;
-
- return {
- cache: cacheState,
- coalesced: false,
- response: {
- state: "ready" as const,
- tile,
- item: service,
- },
- };
- },
- };
-
- await loadDashboardTileResponse(tile, {
- logTileResolution: (event) => logs.push(event),
- now: () => now,
- refreshSeedDocument: true,
- seedIfEmpty: true,
- tileCache,
- });
- now += 10;
- await loadDashboardTileResponse(tile, {
- logTileResolution: (event) => logs.push(event),
- now: () => now,
- refreshSeedDocument: true,
- seedIfEmpty: true,
- tileCache,
- });
-
- expect(logs).toEqual([
- expect.objectContaining({
- cache: "miss",
- coalesced: false,
- durationMs: 7,
- status: "ready",
- tileKey: dashboardTileCacheKey(tile),
- }),
- expect.objectContaining({
- cache: "hit",
- coalesced: false,
- durationMs: 0,
- status: "ready",
- tileKey: dashboardTileCacheKey(tile),
- }),
- ]);
- });
-
- test("keeps telemetry tiles cached for fifteen seconds", async () => {
- const cache = createDashboardTileCache();
- let now = 1_000;
- const fetch = telemetryFetch();
- const tile = { kind: "telemetry", id: "infra-ram" } as const;
-
- await loadDashboardTileResponse(tile, {
- fetch,
- now: () => now,
- prometheusBaseUrl: "https://prometheus.example",
- refreshSeedDocument: true,
- seedIfEmpty: true,
- tileCache: cache,
- });
- await loadDashboardTileResponse(tile, {
- fetch,
- now: () => now + 14_999,
- prometheusBaseUrl: "https://prometheus.example",
- refreshSeedDocument: true,
- seedIfEmpty: true,
- tileCache: cache,
- });
- now += 15_001;
- await loadDashboardTileResponse(tile, {
- fetch,
- now: () => now,
- prometheusBaseUrl: "https://prometheus.example",
- refreshSeedDocument: true,
- seedIfEmpty: true,
- tileCache: cache,
- });
-
- expect(fetch).toHaveBeenCalledTimes(4);
- });
-
- test("keeps weather module tiles cached for ten minutes", async () => {
- const cache = createDashboardTileCache();
- let now = 1_000;
- const fetch = vi.fn(async () =>
- jsonResponse({
- current: {
- apparent_temperature: 19,
- temperature_2m: 20,
- weather_code: 0,
- wind_speed_10m: 11,
- },
- }),
- );
- const tile = { kind: "module", id: "weather-amsterdam" } as const;
-
- await loadDashboardTileResponse(tile, {
- fetch,
- now: () => now,
- refreshSeedDocument: true,
- seedIfEmpty: true,
- tileCache: cache,
- });
- await loadDashboardTileResponse(tile, {
- fetch,
- now: () => now + 599_999,
- refreshSeedDocument: true,
- seedIfEmpty: true,
- tileCache: cache,
- });
- now += 600_001;
- await loadDashboardTileResponse(tile, {
- fetch,
- now: () => now,
- refreshSeedDocument: true,
- seedIfEmpty: true,
- tileCache: cache,
- });
-
- expect(fetch).toHaveBeenCalledTimes(2);
- });
-
- test("coalesces concurrent tile requests for the same cache key", async () => {
- const cache = createDashboardTileCache();
- const logs: DashboardTileResolutionLogEvent[] = [];
- let resolveFetch: ((response: Response) => void) | undefined;
- const fetch = vi.fn(() =>
- new Promise((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((_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 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((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}`);
- });
-}
diff --git a/apps/web/src/server/routes/dashboard.ts b/apps/web/src/server/routes/dashboard.ts
deleted file mode 100644
index c42227c..0000000
--- a/apps/web/src/server/routes/dashboard.ts
+++ /dev/null
@@ -1,609 +0,0 @@
-import type { DashboardDocument } from "@dimensionlab/dashboard-model";
-import {
- loadDashboardRuntime,
- type DashboardRuntimeOptions,
- type DashboardRuntimeState,
-} from "$lib/server/dashboard";
-import {
- resolveDashboardDatasources,
- resolveDashboardTile,
- type DashboardTileReference,
- type DashboardTileResolution,
- type DatasourceResolutionOptions,
-} from "$lib/server/datasources";
-
-export interface LoadDashboardResponseOptions
- extends Pick<
- DashboardRuntimeOptions,
- "refreshSeedDocument" | "seedIfEmpty" | "seedDocument"
- >,
- DatasourceResolutionOptions {
- disableLiveDatasources?: boolean;
- hydrateLiveDatasources?: boolean;
- logTileResolution?: (event: DashboardTileResolutionLogEvent) => void;
- now?: () => number;
- tileCache?: DashboardTileCache;
-}
-
-export interface DashboardEventsRouteOptions extends LoadDashboardResponseOptions {
- signal?: AbortSignal;
-}
-
-interface DashboardTileCacheEntry {
- expiresAt: number;
- response: DashboardTileResolution;
-}
-
-export interface DashboardTileCache {
- resolve(
- key: string,
- ttlMs: number,
- now: number,
- load: () => Promise,
- ): Promise;
-}
-
-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();
- const inFlight = new Map>();
-
- return {
- async resolve(key, ttlMs, now, load) {
- const cached = entries.get(key);
- if (cached && cached.expiresAt > now) {
- return {
- cache: "hit",
- coalesced: false,
- response: cached.response,
- };
- }
-
- const active = inFlight.get(key);
- if (active) {
- return active
- .then((response) => ({
- cache: "miss" as const,
- coalesced: true,
- response,
- }))
- .catch((error) => {
- throw new DashboardTileCacheResolutionError(error, {
- cache: "miss",
- coalesced: true,
- });
- });
- }
-
- const request = load()
- .then((response) => {
- if (response.state === "ready") {
- entries.set(key, {
- expiresAt: now + ttlMs,
- response,
- });
- }
-
- return response;
- })
- .finally(() => {
- inFlight.delete(key);
- });
-
- inFlight.set(key, request);
- return request.then((response) => ({
- cache: "miss" as const,
- coalesced: false,
- response,
- }));
- },
- };
-}
-
-export async function loadDashboardResponse(
- options: LoadDashboardResponseOptions = {},
-): Promise {
- const liveHydrationEnabled =
- !options.disableLiveDatasources &&
- process.env.DISABLE_LIVE_DATASOURCES !== "1";
- const dashboard = loadDashboardRuntime(undefined, {
- refreshSeedDocument: options.refreshSeedDocument ?? true,
- seedIfEmpty: options.seedIfEmpty ?? true,
- seedDocument: options.seedDocument,
- });
-
- if (dashboard.state !== "ready") {
- return dashboard;
- }
-
- if (
- !options.hydrateLiveDatasources ||
- options.disableLiveDatasources ||
- process.env.DISABLE_LIVE_DATASOURCES === "1"
- ) {
- return {
- ...dashboard,
- liveDatasourceHydration: {
- enabled: liveHydrationEnabled,
- },
- };
- }
-
- return {
- ...dashboard,
- document: await resolveDashboardDatasources(dashboard.document, options),
- liveDatasourceHydration: {
- enabled: false,
- },
- };
-}
-
-export async function loadDashboardTileResponse(
- tile: DashboardTileReference,
- options: LoadDashboardResponseOptions = {},
-): Promise {
- 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 {
- return {
- state: "ready",
- tiles: await resolveDashboardTilesBatch(tiles, options),
- };
-}
-
-export async function handleDashboardRoute(): Promise {
- return Response.json(await loadDashboardResponse());
-}
-
-export async function handleDashboardTileRoute(
- pathname: string,
- options: LoadDashboardResponseOptions = {},
-): Promise {
- 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 {
- 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 {
- const stream = new ReadableStream({
- 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 {
- 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 {
- 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,
- ) {
- 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;
-}
diff --git a/apps/web/src/vite-env.d.ts b/apps/web/src/vite-env.d.ts
deleted file mode 100644
index 2e6c10b..0000000
--- a/apps/web/src/vite-env.d.ts
+++ /dev/null
@@ -1,3 +0,0 @@
-///
-
-declare module "*.css" {}
diff --git a/apps/web/static/mockServiceWorker.js b/apps/web/static/mockServiceWorker.js
deleted file mode 100644
index 33dde9e..0000000
--- a/apps/web/static/mockServiceWorker.js
+++ /dev/null
@@ -1,349 +0,0 @@
-/* eslint-disable */
-/* tslint:disable */
-
-/**
- * Mock Service Worker.
- * @see https://github.com/mswjs/msw
- * - Please do NOT modify this file.
- */
-
-const PACKAGE_VERSION = '2.14.6'
-const INTEGRITY_CHECKSUM = '4db4a41e972cec1b64cc569c66952d82'
-const IS_MOCKED_RESPONSE = Symbol('isMockedResponse')
-const activeClientIds = new Set()
-
-addEventListener('install', function () {
- self.skipWaiting()
-})
-
-addEventListener('activate', function (event) {
- event.waitUntil(self.clients.claim())
-})
-
-addEventListener('message', async function (event) {
- const clientId = Reflect.get(event.source || {}, 'id')
-
- if (!clientId || !self.clients) {
- return
- }
-
- const client = await self.clients.get(clientId)
-
- if (!client) {
- return
- }
-
- const allClients = await self.clients.matchAll({
- type: 'window',
- })
-
- switch (event.data) {
- case 'KEEPALIVE_REQUEST': {
- sendToClient(client, {
- type: 'KEEPALIVE_RESPONSE',
- })
- break
- }
-
- case 'INTEGRITY_CHECK_REQUEST': {
- sendToClient(client, {
- type: 'INTEGRITY_CHECK_RESPONSE',
- payload: {
- packageVersion: PACKAGE_VERSION,
- checksum: INTEGRITY_CHECKSUM,
- },
- })
- break
- }
-
- case 'MOCK_ACTIVATE': {
- activeClientIds.add(clientId)
-
- sendToClient(client, {
- type: 'MOCKING_ENABLED',
- payload: {
- client: {
- id: client.id,
- frameType: client.frameType,
- },
- },
- })
- break
- }
-
- case 'CLIENT_CLOSED': {
- activeClientIds.delete(clientId)
-
- const remainingClients = allClients.filter((client) => {
- return client.id !== clientId
- })
-
- // Unregister itself when there are no more clients
- if (remainingClients.length === 0) {
- self.registration.unregister()
- }
-
- break
- }
- }
-})
-
-addEventListener('fetch', function (event) {
- const requestInterceptedAt = Date.now()
-
- // Bypass navigation requests.
- if (event.request.mode === 'navigate') {
- return
- }
-
- // Opening the DevTools triggers the "only-if-cached" request
- // that cannot be handled by the worker. Bypass such requests.
- if (
- event.request.cache === 'only-if-cached' &&
- event.request.mode !== 'same-origin'
- ) {
- return
- }
-
- // Bypass all requests when there are no active clients.
- // Prevents the self-unregistered worked from handling requests
- // after it's been terminated (still remains active until the next reload).
- if (activeClientIds.size === 0) {
- return
- }
-
- const requestId = crypto.randomUUID()
- event.respondWith(handleRequest(event, requestId, requestInterceptedAt))
-})
-
-/**
- * @param {FetchEvent} event
- * @param {string} requestId
- * @param {number} requestInterceptedAt
- */
-async function handleRequest(event, requestId, requestInterceptedAt) {
- const client = await resolveMainClient(event)
- const requestCloneForEvents = event.request.clone()
- const response = await getResponse(
- event,
- client,
- requestId,
- requestInterceptedAt,
- )
-
- // Send back the response clone for the "response:*" life-cycle events.
- // Ensure MSW is active and ready to handle the message, otherwise
- // this message will pend indefinitely.
- if (client && activeClientIds.has(client.id)) {
- const serializedRequest = await serializeRequest(requestCloneForEvents)
-
- // Clone the response so both the client and the library could consume it.
- const responseClone = response.clone()
-
- sendToClient(
- client,
- {
- type: 'RESPONSE',
- payload: {
- isMockedResponse: IS_MOCKED_RESPONSE in response,
- request: {
- id: requestId,
- ...serializedRequest,
- },
- response: {
- type: responseClone.type,
- status: responseClone.status,
- statusText: responseClone.statusText,
- headers: Object.fromEntries(responseClone.headers.entries()),
- body: responseClone.body,
- },
- },
- },
- responseClone.body ? [serializedRequest.body, responseClone.body] : [],
- )
- }
-
- return response
-}
-
-/**
- * Resolve the main client for the given event.
- * Client that issues a request doesn't necessarily equal the client
- * that registered the worker. It's with the latter the worker should
- * communicate with during the response resolving phase.
- * @param {FetchEvent} event
- * @returns {Promise}
- */
-async function resolveMainClient(event) {
- const client = await self.clients.get(event.clientId)
-
- if (activeClientIds.has(event.clientId)) {
- return client
- }
-
- if (client?.frameType === 'top-level') {
- return client
- }
-
- const allClients = await self.clients.matchAll({
- type: 'window',
- })
-
- return allClients
- .filter((client) => {
- // Get only those clients that are currently visible.
- return client.visibilityState === 'visible'
- })
- .find((client) => {
- // Find the client ID that's recorded in the
- // set of clients that have registered the worker.
- return activeClientIds.has(client.id)
- })
-}
-
-/**
- * @param {FetchEvent} event
- * @param {Client | undefined} client
- * @param {string} requestId
- * @param {number} requestInterceptedAt
- * @returns {Promise}
- */
-async function getResponse(event, client, requestId, requestInterceptedAt) {
- // Clone the request because it might've been already used
- // (i.e. its body has been read and sent to the client).
- const requestClone = event.request.clone()
-
- function passthrough() {
- // Cast the request headers to a new Headers instance
- // so the headers can be manipulated with.
- const headers = new Headers(requestClone.headers)
-
- // Remove the "accept" header value that marked this request as passthrough.
- // This prevents request alteration and also keeps it compliant with the
- // user-defined CORS policies.
- const acceptHeader = headers.get('accept')
- if (acceptHeader) {
- const values = acceptHeader.split(',').map((value) => value.trim())
- const filteredValues = values.filter(
- (value) => value !== 'msw/passthrough',
- )
-
- if (filteredValues.length > 0) {
- headers.set('accept', filteredValues.join(', '))
- } else {
- headers.delete('accept')
- }
- }
-
- return fetch(requestClone, { headers })
- }
-
- // Bypass mocking when the client is not active.
- if (!client) {
- return passthrough()
- }
-
- // Bypass initial page load requests (i.e. static assets).
- // The absence of the immediate/parent client in the map of the active clients
- // means that MSW hasn't dispatched the "MOCK_ACTIVATE" event yet
- // and is not ready to handle requests.
- if (!activeClientIds.has(client.id)) {
- return passthrough()
- }
-
- // Notify the client that a request has been intercepted.
- const serializedRequest = await serializeRequest(event.request)
- const clientMessage = await sendToClient(
- client,
- {
- type: 'REQUEST',
- payload: {
- id: requestId,
- interceptedAt: requestInterceptedAt,
- ...serializedRequest,
- },
- },
- [serializedRequest.body],
- )
-
- switch (clientMessage.type) {
- case 'MOCK_RESPONSE': {
- return respondWithMock(clientMessage.data)
- }
-
- case 'PASSTHROUGH': {
- return passthrough()
- }
- }
-
- return passthrough()
-}
-
-/**
- * @param {Client} client
- * @param {any} message
- * @param {Array} transferrables
- * @returns {Promise}
- */
-function sendToClient(client, message, transferrables = []) {
- return new Promise((resolve, reject) => {
- const channel = new MessageChannel()
-
- channel.port1.onmessage = (event) => {
- if (event.data && event.data.error) {
- return reject(event.data.error)
- }
-
- resolve(event.data)
- }
-
- client.postMessage(message, [
- channel.port2,
- ...transferrables.filter(Boolean),
- ])
- })
-}
-
-/**
- * @param {Response} response
- * @returns {Response}
- */
-function respondWithMock(response) {
- // Setting response status code to 0 is a no-op.
- // However, when responding with a "Response.error()", the produced Response
- // instance will have status code set to 0. Since it's not possible to create
- // a Response instance with status code 0, handle that use-case separately.
- if (response.status === 0) {
- return Response.error()
- }
-
- const mockedResponse = new Response(response.body, response)
-
- Reflect.defineProperty(mockedResponse, IS_MOCKED_RESPONSE, {
- value: true,
- enumerable: true,
- })
-
- return mockedResponse
-}
-
-/**
- * @param {Request} request
- */
-async function serializeRequest(request) {
- return {
- url: request.url,
- mode: request.mode,
- method: request.method,
- headers: Object.fromEntries(request.headers.entries()),
- cache: request.cache,
- credentials: request.credentials,
- destination: request.destination,
- integrity: request.integrity,
- redirect: request.redirect,
- referrer: request.referrer,
- referrerPolicy: request.referrerPolicy,
- body: await request.arrayBuffer(),
- keepalive: request.keepalive,
- }
-}
diff --git a/apps/web/tests/e2e/dashboard.spec.ts b/apps/web/tests/e2e/dashboard.spec.ts
deleted file mode 100644
index 3eb0944..0000000
--- a/apps/web/tests/e2e/dashboard.spec.ts
+++ /dev/null
@@ -1,381 +0,0 @@
-import AxeBuilder from "@axe-core/playwright";
-import { expect, test, type Page } from "@playwright/test";
-
-const linkedServiceIds = [
- "vaultwarden",
- "forgejo",
- "wiki",
- "aws-start",
- "adguard-primary",
- "adguard-secondary",
- "grafana",
- "uptime-kuma",
- "prometheus",
- "backrest",
- "n8n",
- "open-webui",
- "comfyui",
- "models",
- "adminer",
- "assistant",
- "suna",
- "cockpit-infra",
- "cockpit-gpu",
- "cockpit-network-core",
- "forgejo-ssh-relay",
-];
-
-test.describe("dashboard page QA gate", () => {
- test("renders the model-driven dashboard on desktop", async ({
- page,
- }, testInfo) => {
- test.skip(testInfo.project.name !== "chromium-desktop");
-
- await page.goto("/");
- await waitForDashboardReady(page);
-
- await expect(page.getByRole("main")).toHaveAttribute(
- "aria-labelledby",
- /title/,
- );
- await expect(
- page.getByRole("heading", { level: 1, name: "System Overview" }),
- ).toBeVisible();
- await expect(page.getByText("Infra RAM")).toBeVisible();
- await expect(page.getByRole("link", { name: /Vaultwarden/i })).toBeVisible();
- await expect(page.locator("[data-model-id='vaultwarden']")).toBeVisible();
- await expect(page.locator(".service-row .icon-glyph svg").first()).toBeVisible();
-
- const bodyBox = await page.locator("body").boundingBox();
- expect(bodyBox?.width).toBeGreaterThan(1000);
-
- await expect(page).toHaveScreenshot("dashboard-desktop.png", {
- fullPage: true,
- });
- });
-
- test("fits the operational dashboard into a 1470 by 956 viewport", async ({
- page,
- }, testInfo) => {
- test.skip(testInfo.project.name !== "chromium-desktop");
-
- await page.setViewportSize({ width: 1470, height: 956 });
- await page.goto("/");
- await waitForDashboardReady(page);
-
- const metrics = await page.evaluate(() => {
- const footer = document.querySelector("[data-model-id='footer-status']");
- const runtime = document.querySelector("[data-model-id='runtime-health']");
- const visibleItems = [
- ...document.querySelectorAll(".telemetry-card"),
- ...document.querySelectorAll(".service-row"),
- ...document.querySelectorAll(".footer-cell"),
- ].map((element) => {
- const rect = element.getBoundingClientRect();
- return {
- bottom: rect.bottom,
- height: rect.height,
- id: element.getAttribute("data-model-id"),
- top: rect.top,
- width: rect.width,
- };
- });
-
- return {
- clippedItems: visibleItems.filter((item) =>
- item.top < 0 ||
- item.bottom > window.innerHeight ||
- item.width <= 0 ||
- item.height <= 0
- ),
- footerCellCount: document.querySelectorAll(".footer-cell").length,
- scrollWidth: document.documentElement.scrollWidth,
- scrollHeight: document.documentElement.scrollHeight,
- serviceRowCount: document.querySelectorAll(".service-row").length,
- telemetryCardCount: document.querySelectorAll(".telemetry-card").length,
- footerBottom: footer?.getBoundingClientRect().bottom ?? 0,
- runtimeBottom: runtime?.getBoundingClientRect().bottom ?? 0,
- };
- });
-
- expect(metrics.scrollWidth).toBeLessThanOrEqual(1470);
- expect(metrics.scrollHeight).toBeLessThanOrEqual(956);
- expect(metrics.runtimeBottom).toBeLessThanOrEqual(956);
- expect(metrics.footerBottom).toBeLessThanOrEqual(956);
- expect(metrics.telemetryCardCount).toBe(16);
- expect(metrics.serviceRowCount).toBe(27);
- expect(metrics.footerCellCount).toBe(5);
- expect(metrics.clippedItems).toEqual([]);
- });
-
- test("keeps intermediate viewports scrollable without horizontal clipping", async ({
- page,
- }, testInfo) => {
- test.skip(testInfo.project.name !== "chromium-desktop");
-
- for (const viewport of [
- { width: 900, height: 956 },
- { width: 1024, height: 768 },
- ]) {
- await page.setViewportSize(viewport);
- await page.goto("/");
- await waitForDashboardReady(page);
-
- const metrics = await page.evaluate(() => {
- const footer = document.querySelector("[data-model-id='footer-status']");
- const trackedElements = [
- document.querySelector(".dashboard-frame__header"),
- ...document.querySelectorAll(".telemetry-card"),
- ...document.querySelectorAll(".service-panel"),
- document.querySelector("[data-model-id='runtime-health']"),
- footer,
- ].filter((element): element is Element => Boolean(element));
-
- const footerRect = footer?.getBoundingClientRect();
- const clippedRight = trackedElements
- .map((element) => {
- const rect = element.getBoundingClientRect();
- return {
- id: element.getAttribute("data-model-id") || element.className,
- right: rect.right,
- width: rect.width,
- };
- })
- .filter((item) => item.right > window.innerWidth + 1 || item.width <= 0);
-
- return {
- clippedRight,
- footerBottomInDocument: (footerRect?.bottom ?? 0) + window.scrollY,
- frameOverflow: window.getComputedStyle(
- document.querySelector(".dashboard-frame") as Element,
- ).overflow,
- scrollHeight: document.documentElement.scrollHeight,
- scrollWidth: document.documentElement.scrollWidth,
- };
- });
-
- expect(metrics.scrollWidth).toBeLessThanOrEqual(viewport.width);
- expect(metrics.scrollHeight).toBeGreaterThanOrEqual(
- Math.ceil(metrics.footerBottomInDocument),
- );
- expect(metrics.frameOverflow).not.toBe("hidden");
- expect(metrics.clippedRight).toEqual([]);
- }
- });
-
- test("renders bookmark rows as a compact divider list", async ({ page }, testInfo) => {
- test.skip(testInfo.project.name !== "chromium-desktop");
-
- await page.goto("/");
- await waitForDashboardReady(page);
-
- const bookmarkDensity = await page.evaluate(() => {
- const panelBody = document.querySelector(".service-panel .panel__body");
- const rows = Array.from(document.querySelectorAll(".service-panel .service-row"));
- const first = rows[0];
- const second = rows[1];
- if (!panelBody || !first || !second) {
- throw new Error("expected at least two bookmark rows");
- }
-
- const bodyStyle = window.getComputedStyle(panelBody);
- const rowStyle = window.getComputedStyle(first);
- const firstRect = first.getBoundingClientRect();
- const secondRect = second.getBoundingClientRect();
-
- return {
- backgroundColor: rowStyle.backgroundColor,
- borderBottomWidth: Number.parseFloat(rowStyle.borderBottomWidth),
- borderLeftWidth: Number.parseFloat(rowStyle.borderLeftWidth),
- borderRightWidth: Number.parseFloat(rowStyle.borderRightWidth),
- columnGap: Number.parseFloat(rowStyle.columnGap),
- paddingBottom: Number.parseFloat(rowStyle.paddingBottom),
- paddingTop: Number.parseFloat(rowStyle.paddingTop),
- panelGap: Number.parseFloat(bodyStyle.rowGap),
- rowGap: secondRect.top - firstRect.bottom,
- };
- });
-
- expect(bookmarkDensity.panelGap).toBe(0);
- expect(bookmarkDensity.rowGap).toBeLessThanOrEqual(1);
- expect(bookmarkDensity.paddingTop).toBeLessThanOrEqual(3);
- expect(bookmarkDensity.paddingBottom).toBeLessThanOrEqual(3);
- expect(bookmarkDensity.columnGap).toBeLessThanOrEqual(4);
- expect(bookmarkDensity.borderBottomWidth).toBeGreaterThanOrEqual(1);
- expect(bookmarkDensity.borderLeftWidth).toBe(0);
- expect(bookmarkDensity.borderRightWidth).toBe(0);
- expect(bookmarkDensity.backgroundColor).toBe("rgba(0, 0, 0, 0)");
- });
-
- test("balances dashboard typography at the target viewport", async ({ page }, testInfo) => {
- test.skip(testInfo.project.name !== "chromium-desktop");
-
- await page.setViewportSize({ width: 1470, height: 956 });
- await page.goto("/");
- await waitForDashboardReady(page);
-
- const typeScale = await page.evaluate(() => {
- const fontSize = (selector: string) => {
- const element = document.querySelector(selector);
- if (!element) throw new Error(`missing ${selector}`);
- return Number.parseFloat(window.getComputedStyle(element).fontSize);
- };
-
- return {
- h1: fontSize("h1"),
- panelTitle: fontSize(".service-panel h2"),
- serviceDescription: fontSize(".service-row p"),
- serviceLabel: fontSize(".service-row h3"),
- telemetryLabel: fontSize(".telemetry-card h3"),
- telemetryValue: fontSize(".telemetry-card strong"),
- };
- });
-
- expect(typeScale.serviceLabel).toBeGreaterThanOrEqual(11.4);
- expect(typeScale.serviceDescription).toBeGreaterThanOrEqual(8.8);
- expect(typeScale.h1).toBeLessThanOrEqual(49);
- expect(typeScale.panelTitle).toBeLessThanOrEqual(28);
- expect(typeScale.telemetryLabel).toBeLessThanOrEqual(9.5);
- expect(typeScale.telemetryValue).toBeLessThanOrEqual(36);
- });
-
- test("keeps the first screen usable on mobile", async ({ page }, testInfo) => {
- test.skip(testInfo.project.name !== "chromium-mobile");
-
- await page.goto("/");
- await waitForDashboardReady(page);
-
- await expect(
- page.getByRole("heading", { level: 1, name: "System Overview" }),
- ).toBeVisible();
- await expect(page.getByLabel("Service groups")).toBeVisible();
- await expect(page.locator(".service-row .icon-glyph svg").first()).toBeVisible();
-
- await expect(page).toHaveScreenshot("dashboard-mobile.png", {
- fullPage: true,
- });
- });
-
- test("exposes usable landmarks and a visible keyboard focus state", async ({
- page,
- }) => {
- await page.goto("/");
- await waitForDashboardReady(page);
-
- await expect(page.getByRole("main")).toHaveCount(1);
- await page.keyboard.press("Tab");
- const themeToggle = page.getByRole("button", { name: "Light theme" });
- await expect(themeToggle).toBeFocused();
- await expect(themeToggle).toHaveAttribute("aria-pressed", "false");
- const themeFocusBoxShadow = await themeToggle.evaluate((element) => {
- return window.getComputedStyle(element).boxShadow;
- });
- expect(themeFocusBoxShadow).not.toBe("none");
-
- for (const serviceId of linkedServiceIds) {
- await page.keyboard.press("Tab");
-
- const focused = page.locator(":focus");
- await expect(focused).toHaveAttribute("data-model-id", serviceId);
-
- const focusBoxShadow = await focused.evaluate((element) => {
- return window.getComputedStyle(element).boxShadow;
- });
- expect(focusBoxShadow).not.toBe("none");
- }
- });
-
- test("passes automated accessibility checks", async ({ page }) => {
- await page.goto("/");
- await waitForDashboardReady(page);
-
- const results = await new AxeBuilder({ page }).analyze();
- expect(results.violations).toEqual([]);
- });
-
- test("passes automated accessibility checks in light mode", async ({
- page,
- }, testInfo) => {
- test.skip(testInfo.project.name !== "chromium-desktop");
-
- await page.goto("/");
- await waitForDashboardReady(page);
- await page.getByRole("button", { name: "Light theme" }).click();
- await expect(page.locator("html")).toHaveAttribute("data-ui-theme", "light");
- await expect(page.getByRole("button", { name: "Light theme" })).toHaveAttribute(
- "aria-pressed",
- "true",
- );
-
- const results = await new AxeBuilder({ page }).analyze();
- expect(results.violations).toEqual([]);
-
- await expect(page).toHaveScreenshot("dashboard-light-desktop.png", {
- fullPage: true,
- });
- });
-
- test("honors reduced-motion preferences", async ({ page }) => {
- await page.emulateMedia({ reducedMotion: "reduce" });
-
- await page.goto("/");
- await waitForDashboardReady(page);
- const durations = await page.evaluate(() => {
- const element = document.createElement("div");
- element.style.animation = "qa-motion-check 10s infinite";
- element.style.transition = "opacity 10s linear";
- document.body.append(element);
-
- const styles = window.getComputedStyle(element);
- return {
- animation: styles.animationDuration,
- transition: styles.transitionDuration,
- };
- });
-
- expect(cssDurationToMilliseconds(durations.animation)).toBeLessThanOrEqual(
- 0.01,
- );
- expect(cssDurationToMilliseconds(durations.transition)).toBeLessThanOrEqual(
- 0.01,
- );
- });
-
- test("toggles the dashboard between dark and light themes", async ({ page }) => {
- await page.goto("/");
- await waitForDashboardReady(page);
-
- await expect(page.locator("html")).toHaveAttribute("data-ui-theme", "dark");
-
- const switchToLight = page.getByRole("button", {
- name: "Light theme",
- });
- await expect(switchToLight).toBeVisible();
- await expect(switchToLight).toHaveAttribute("aria-pressed", "false");
- await switchToLight.click();
-
- await expect(page.locator("html")).toHaveAttribute("data-ui-theme", "light");
- await expect(page.getByRole("button", { name: "Light theme" })).toHaveAttribute(
- "aria-pressed",
- "true",
- );
-
- await page.reload();
- await waitForDashboardReady(page);
-
- await expect(page.locator("html")).toHaveAttribute("data-ui-theme", "light");
- });
-});
-
-async function waitForDashboardReady(page: Page): Promise {
- await expect(
- page.getByRole("heading", { level: 1, name: "System Overview" }),
- ).toBeVisible();
- await expect(page.locator("[data-model-id='vaultwarden']")).toBeVisible();
-}
-
-function cssDurationToMilliseconds(duration: string): number {
- if (duration.endsWith("ms")) return Number.parseFloat(duration);
- if (duration.endsWith("s")) return Number.parseFloat(duration) * 1000;
- return Number.NaN;
-}
diff --git a/apps/web/tests/e2e/dashboard.spec.ts-snapshots/dashboard-desktop-chromium-desktop-linux.png b/apps/web/tests/e2e/dashboard.spec.ts-snapshots/dashboard-desktop-chromium-desktop-linux.png
deleted file mode 100644
index a1c0ada..0000000
Binary files a/apps/web/tests/e2e/dashboard.spec.ts-snapshots/dashboard-desktop-chromium-desktop-linux.png and /dev/null differ
diff --git a/apps/web/tests/e2e/dashboard.spec.ts-snapshots/dashboard-light-desktop-chromium-desktop-linux.png b/apps/web/tests/e2e/dashboard.spec.ts-snapshots/dashboard-light-desktop-chromium-desktop-linux.png
deleted file mode 100644
index f5d5163..0000000
Binary files a/apps/web/tests/e2e/dashboard.spec.ts-snapshots/dashboard-light-desktop-chromium-desktop-linux.png and /dev/null differ
diff --git a/apps/web/tests/e2e/dashboard.spec.ts-snapshots/dashboard-mobile-chromium-mobile-linux.png b/apps/web/tests/e2e/dashboard.spec.ts-snapshots/dashboard-mobile-chromium-mobile-linux.png
deleted file mode 100644
index 48293a4..0000000
Binary files a/apps/web/tests/e2e/dashboard.spec.ts-snapshots/dashboard-mobile-chromium-mobile-linux.png and /dev/null differ
diff --git a/apps/web/tests/e2e/storybook-server.ts b/apps/web/tests/e2e/storybook-server.ts
deleted file mode 100644
index 7544654..0000000
--- a/apps/web/tests/e2e/storybook-server.ts
+++ /dev/null
@@ -1,35 +0,0 @@
-import { existsSync } from "node:fs";
-import { resolve, sep } from "node:path";
-
-const root = resolve(process.cwd(), "packages/ui/storybook-static");
-const port = Number(process.env.STORYBOOK_STATIC_PORT || 6007);
-
-if (!existsSync(resolve(root, "iframe.html"))) {
- throw new Error(
- "packages/ui/storybook-static is missing. Run bun run build-storybook first.",
- );
-}
-
-Bun.serve({
- hostname: "127.0.0.1",
- port,
- async fetch(request) {
- const url = new URL(request.url);
- const pathname = decodeURIComponent(url.pathname);
- const relativePath = pathname === "/" ? "/index.html" : pathname;
- const filePath = resolve(root, `.${relativePath}`);
-
- if (!filePath.startsWith(`${root}${sep}`)) {
- return new Response("Forbidden", { status: 403 });
- }
-
- const file = Bun.file(filePath);
- if (!(await file.exists())) {
- return new Response("Not found", { status: 404 });
- }
-
- return new Response(file);
- },
-});
-
-console.log(`Storybook static listening on http://127.0.0.1:${port}`);
diff --git a/apps/web/tests/e2e/storybook.spec.ts b/apps/web/tests/e2e/storybook.spec.ts
deleted file mode 100644
index 47a58ba..0000000
--- a/apps/web/tests/e2e/storybook.spec.ts
+++ /dev/null
@@ -1,40 +0,0 @@
-import { expect, test } from "@playwright/test";
-
-const storybookPort = Number(process.env.PLAYWRIGHT_STORYBOOK_PORT || 6007);
-const storybookURL = `http://127.0.0.1:${storybookPort}`;
-
-test.describe("Storybook theme QA", () => {
- test("renders the ThemeToggle light story on the light canvas", async ({
- page,
- }, testInfo) => {
- test.skip(testInfo.project.name !== "chromium-desktop");
-
- const consoleMessages: string[] = [];
- page.on("console", (message) => {
- if (["error", "warning"].includes(message.type())) {
- consoleMessages.push(message.text());
- }
- });
-
- const url = `${storybookURL}/iframe.html?id=ui-themetoggle--light&viewMode=story`;
- await page.goto(url, { waitUntil: "networkidle" });
-
- await expect(page.locator("html")).toHaveAttribute("data-ui-theme", "light");
- await expect(
- page.getByRole("button", { name: "Light theme" }),
- ).toBeVisible();
- await expect(page.getByRole("button", { name: "Light theme" })).toHaveAttribute(
- "aria-pressed",
- "true",
- );
- await expect(page.locator("body")).toHaveCSS(
- "background-color",
- "rgb(238, 242, 231)",
- );
- expect(
- consoleMessages.filter((message) =>
- message.includes("Global args/argTypes can only be set globally"),
- ),
- ).toEqual([]);
- });
-});
diff --git a/apps/web/tsconfig.json b/apps/web/tsconfig.json
deleted file mode 100644
index 120931b..0000000
--- a/apps/web/tsconfig.json
+++ /dev/null
@@ -1,19 +0,0 @@
-{
- "extends": "../../tsconfig.base.json",
- "compilerOptions": {
- "baseUrl": ".",
- "paths": {
- "$lib/*": ["src/lib/*"]
- },
- "types": ["node", "bun-types", "react", "react-dom", "vite/client"]
- },
- "include": [
- "src/**/*.ts",
- "src/**/*.tsx",
- "tests/**/*.ts",
- "vite.config.ts",
- "playwright.config.ts",
- "drizzle.config.ts"
- ],
- "exclude": ["build", "dist", "node_modules"]
-}
diff --git a/apps/web/vite.config.ts b/apps/web/vite.config.ts
deleted file mode 100644
index ceff746..0000000
--- a/apps/web/vite.config.ts
+++ /dev/null
@@ -1,34 +0,0 @@
-import { fileURLToPath } from "node:url";
-import tailwindcss from "@tailwindcss/vite";
-import react from "@vitejs/plugin-react";
-import { defineConfig, configDefaults } from "vitest/config";
-import type { UserConfig } from "vite";
-
-export function createDevServerConfig(
- env: NodeJS.ProcessEnv = process.env,
-): UserConfig["server"] {
- const apiTarget = env.DASHBOARD_DEV_API_TARGET;
- if (!apiTarget) return undefined;
-
- return {
- proxy: {
- "/api": {
- target: apiTarget,
- changeOrigin: true,
- },
- },
- };
-}
-
-export default defineConfig({
- plugins: [react(), tailwindcss()],
- resolve: {
- alias: {
- $lib: fileURLToPath(new URL("./src/lib", import.meta.url)),
- },
- },
- server: createDevServerConfig(),
- test: {
- exclude: [...configDefaults.exclude, "tests/e2e/**"],
- },
-});
diff --git a/bun.lock b/bun.lock
index d6915d0..db8e635 100644
--- a/bun.lock
+++ b/bun.lock
@@ -4,168 +4,44 @@
"workspaces": {
"": {
"name": "dimensionlab-website",
- "devDependencies": {
- "turbo": "^2.5.0",
- },
- },
- "apps/web": {
- "name": "@dimensionlab/web",
- "version": "0.0.1",
"dependencies": {
- "@dimensionlab/dashboard-model": "workspace:*",
- "@dimensionlab/ui": "workspace:*",
+ "@iconify/svelte": "^5.2.2",
"@sinclair/typebox": "^0.34.49",
"ajv": "^8.20.0",
"ajv-formats": "^3.0.1",
"drizzle-orm": "^0.45.2",
- "react": "^19.2.7",
- "react-dom": "^19.2.7",
- "tw-animate-css": "^1.4.0",
- },
- "devDependencies": {
- "@axe-core/playwright": "^4.11.3",
- "@playwright/test": "^1.61.0",
- "@tailwindcss/vite": "^4.3.1",
- "@types/bun": "^1.3.14",
- "@types/node": "^25.9.3",
- "@types/react": "^19.2.17",
- "@types/react-dom": "^19.2.3",
- "@vitejs/plugin-react": "^6.0.2",
- "bun-types": "^1.3.14",
- "drizzle-kit": "^0.31.10",
- "msw": "^2.14.6",
- "shadcn": "^4.11.0",
- "tailwindcss": "^4.3.1",
- "typescript": "^6.0.3",
- "vite": "^8.0.16",
- "vitest": "^4.1.9",
- },
- },
- "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": {
- "name": "@dimensionlab/ui",
- "version": "0.0.1",
- "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",
},
"devDependencies": {
"@storybook/addon-a11y": "^10.4.6",
+ "@storybook/addon-svelte-csf": "^5.1.2",
"@storybook/addon-vitest": "^10.4.6",
- "@storybook/react-vite": "^10.4.6",
+ "@storybook/sveltekit": "^10.4.6",
+ "@sveltejs/adapter-node": "^5.5.4",
+ "@sveltejs/kit": "^2.65.2",
+ "@sveltejs/vite-plugin-svelte": "^7.1.2",
"@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",
+ "drizzle-kit": "^0.31.10",
"storybook": "^10.4.6",
+ "svelte": "^5.56.3",
+ "svelte-check": "^4.6.0",
"typescript": "^6.0.3",
"vite": "^8.0.16",
"vitest": "^4.1.9",
},
- "peerDependencies": {
- "react": "^19.0.0",
- "react-dom": "^19.0.0",
- },
},
},
"packages": {
"@adobe/css-tools": ["@adobe/css-tools@4.5.0", "", {}, "sha512-6OzddxPio9UiWTCemp4N8cYLV2ZN1ncRnV1cVGtve7dhPOtRkleRyx32GQCYSwDYgaHU3USMm84tNsvKzRCa1Q=="],
- "@axe-core/playwright": ["@axe-core/playwright@4.11.3", "", { "dependencies": { "axe-core": "~4.11.4" }, "peerDependencies": { "playwright-core": ">= 1.0.0" } }, "sha512-h/kfksv4F0cVIDlKpT4700OehdRgpvuVskuQ2nb7/JmtWUXpe9ftHAPtwyXGvVSsa6SJ64A9ER7Zrzc/sIvC4w=="],
-
"@babel/code-frame": ["@babel/code-frame@7.29.7", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.29.7", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw=="],
- "@babel/compat-data": ["@babel/compat-data@7.29.7", "", {}, "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg=="],
-
- "@babel/core": ["@babel/core@7.29.7", "", { "dependencies": { "@babel/code-frame": "^7.29.7", "@babel/generator": "^7.29.7", "@babel/helper-compilation-targets": "^7.29.7", "@babel/helper-module-transforms": "^7.29.7", "@babel/helpers": "^7.29.7", "@babel/parser": "^7.29.7", "@babel/template": "^7.29.7", "@babel/traverse": "^7.29.7", "@babel/types": "^7.29.7", "@jridgewell/remapping": "^2.3.5", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", "json5": "^2.2.3", "semver": "^6.3.1" } }, "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA=="],
-
- "@babel/generator": ["@babel/generator@7.29.7", "", { "dependencies": { "@babel/parser": "^7.29.7", "@babel/types": "^7.29.7", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ=="],
-
- "@babel/helper-annotate-as-pure": ["@babel/helper-annotate-as-pure@7.29.7", "", { "dependencies": { "@babel/types": "^7.29.7" } }, "sha512-OoK6239jHPuSQOoS0kfTVKn0b/rVTk0seKq4Gd2UMLtmOVLjDC0ki3e+c90Trqv2gMfvJFqkiljrr568+qddiw=="],
-
- "@babel/helper-compilation-targets": ["@babel/helper-compilation-targets@7.29.7", "", { "dependencies": { "@babel/compat-data": "^7.29.7", "@babel/helper-validator-option": "^7.29.7", "browserslist": "^4.24.0", "lru-cache": "^5.1.1", "semver": "^6.3.1" } }, "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g=="],
-
- "@babel/helper-create-class-features-plugin": ["@babel/helper-create-class-features-plugin@7.29.7", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.29.7", "@babel/helper-member-expression-to-functions": "^7.29.7", "@babel/helper-optimise-call-expression": "^7.29.7", "@babel/helper-replace-supers": "^7.29.7", "@babel/helper-skip-transparent-expression-wrappers": "^7.29.7", "@babel/traverse": "^7.29.7", "semver": "^6.3.1" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-IY3ZD9Tmooqr3TUhc3DUWxiuo8xx1DWLhd5M7hQ+ZWJamqM2BbalrBJb2MisSLoYorOj75U03qULCxQTY9r3hg=="],
-
- "@babel/helper-globals": ["@babel/helper-globals@7.29.7", "", {}, "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA=="],
-
- "@babel/helper-member-expression-to-functions": ["@babel/helper-member-expression-to-functions@7.29.7", "", { "dependencies": { "@babel/traverse": "^7.29.7", "@babel/types": "^7.29.7" } }, "sha512-j+7JYmk1JYDtACIGj0QJqqWZjoUpMoEikQGADMaHgCMCSDqd2+P32rfcibUNrGOMWrlzK1WJBdxrB3JJQZwWtg=="],
-
- "@babel/helper-module-imports": ["@babel/helper-module-imports@7.29.7", "", { "dependencies": { "@babel/traverse": "^7.29.7", "@babel/types": "^7.29.7" } }, "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g=="],
-
- "@babel/helper-module-transforms": ["@babel/helper-module-transforms@7.29.7", "", { "dependencies": { "@babel/helper-module-imports": "^7.29.7", "@babel/helper-validator-identifier": "^7.29.7", "@babel/traverse": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg=="],
-
- "@babel/helper-optimise-call-expression": ["@babel/helper-optimise-call-expression@7.29.7", "", { "dependencies": { "@babel/types": "^7.29.7" } }, "sha512-+kmGVjcT9RGYzoDwdwEqEvGgKe3BYq+O1iGzjFubaNgZHwYHP6lsF2Yghf4kEuv9BV7tYDZ913aBW9am6YKong=="],
-
- "@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.29.7", "", {}, "sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw=="],
-
- "@babel/helper-replace-supers": ["@babel/helper-replace-supers@7.29.7", "", { "dependencies": { "@babel/helper-member-expression-to-functions": "^7.29.7", "@babel/helper-optimise-call-expression": "^7.29.7", "@babel/traverse": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-atfGXWSeCiF4DnKZIfmJfQRkSw9b9gNNXR1kqKjbhG4pGYCOnkp8OcTB8E3NXjBu8NpheSnOeNKz8KT7UNFTmQ=="],
-
- "@babel/helper-skip-transparent-expression-wrappers": ["@babel/helper-skip-transparent-expression-wrappers@7.29.7", "", { "dependencies": { "@babel/traverse": "^7.29.7", "@babel/types": "^7.29.7" } }, "sha512-brcMGQaVzIeUb+6/bs1Av0f8YuNNjKY2JyvfRCsFuFsdKccEQ5Ges2y74D74NZ1Rz8lKJ9ksJkfqwQFJ/iNEyQ=="],
-
- "@babel/helper-string-parser": ["@babel/helper-string-parser@7.29.7", "", {}, "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw=="],
-
"@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.29.7", "", {}, "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg=="],
- "@babel/helper-validator-option": ["@babel/helper-validator-option@7.29.7", "", {}, "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw=="],
-
- "@babel/helpers": ["@babel/helpers@7.29.7", "", { "dependencies": { "@babel/template": "^7.29.7", "@babel/types": "^7.29.7" } }, "sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg=="],
-
- "@babel/parser": ["@babel/parser@7.29.7", "", { "dependencies": { "@babel/types": "^7.29.7" }, "bin": "./bin/babel-parser.js" }, "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg=="],
-
- "@babel/plugin-syntax-jsx": ["@babel/plugin-syntax-jsx@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-TSu8+mHCoEaaCDEZ0I3+6mvTBYR4PCxQwf2z9/r5Tbztv6NaLR3B9thGTTxX2WGuGHJqRiAbKPeGTJ5XWXVg6A=="],
-
- "@babel/plugin-syntax-typescript": ["@babel/plugin-syntax-typescript@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-ngr+82Sh0xMz25TPCZi+nC2iTzjfCdWS2ONXTp/PtSCHCgaCNBpdMqgvJ2ccdLlClVZ7sisIgB914j/JFe+RZA=="],
-
- "@babel/plugin-transform-modules-commonjs": ["@babel/plugin-transform-modules-commonjs@7.29.7", "", { "dependencies": { "@babel/helper-module-transforms": "^7.29.7", "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-j0vCldybPC5b5dwCQOJ21uKtHzt7hxLygJTg9eF1ScfaikEDNfzn94XoW5Fi+seBR0nCyL23xaBFFkq7dTM8XQ=="],
-
- "@babel/plugin-transform-typescript": ["@babel/plugin-transform-typescript@7.29.7", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.29.7", "@babel/helper-create-class-features-plugin": "^7.29.7", "@babel/helper-plugin-utils": "^7.29.7", "@babel/helper-skip-transparent-expression-wrappers": "^7.29.7", "@babel/plugin-syntax-typescript": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-jK52h8LaLc7JarhQV2ofeFMts4H7vnOXnqZNA6fYglBTZewRBE51KWt3BUltW1P+KoPsYkHoJeXePuz4zo2LMw=="],
-
- "@babel/preset-typescript": ["@babel/preset-typescript@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7", "@babel/helper-validator-option": "^7.29.7", "@babel/plugin-syntax-jsx": "^7.29.7", "@babel/plugin-transform-modules-commonjs": "^7.29.7", "@babel/plugin-transform-typescript": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-/Foi8vKY2EVbed/1eZx0gJEEwHAIxogrySI7rULcRIvhZzbvoE/b5qG5Ghc0WKAFKOHA9SD1x7RsFlOYdutIiQ=="],
-
"@babel/runtime": ["@babel/runtime@7.29.7", "", {}, "sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw=="],
- "@babel/template": ["@babel/template@7.29.7", "", { "dependencies": { "@babel/code-frame": "^7.29.7", "@babel/parser": "^7.29.7", "@babel/types": "^7.29.7" } }, "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg=="],
-
- "@babel/traverse": ["@babel/traverse@7.29.7", "", { "dependencies": { "@babel/code-frame": "^7.29.7", "@babel/generator": "^7.29.7", "@babel/helper-globals": "^7.29.7", "@babel/parser": "^7.29.7", "@babel/template": "^7.29.7", "@babel/types": "^7.29.7", "debug": "^4.3.1" } }, "sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw=="],
-
- "@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/web": ["@dimensionlab/web@workspace:apps/web"],
-
- "@dotenvx/dotenvx": ["@dotenvx/dotenvx@1.74.3", "", { "dependencies": { "commander": "^11.1.0", "conf": "^10.2.0", "dotenv": "^17.2.1", "eciesjs": "^0.4.10", "enquirer": "^2.4.1", "env-paths": "^2.2.1", "execa": "^5.1.1", "fdir": "^6.2.0", "ignore": "^5.3.0", "object-treeify": "1.1.33", "open": "^8.4.2", "picomatch": "^4.0.4", "systeminformation": "^5.22.11", "undici": "^7.11.0", "which": "^4.0.0", "yocto-spinner": "^1.1.0" }, "bin": { "dotenvx": "src/cli/dotenvx.js" } }, "sha512-+VjTIiOCApjB0K4a41+SkN18gTetmhU9UN2JD8LHeNUqo/38FmtgvdtWsW/WWN1dyYRGS7XKeAAb/ruy7x1tRw=="],
-
"@drizzle-team/brocli": ["@drizzle-team/brocli@0.10.2", "", {}, "sha512-z33Il7l5dKjUgGULTqBsQBQwckHh5AbIuxhdsIxDDiZAzBOrZO6q9ogcWC65kU382AfynTfgNumVcNIjuIua6w=="],
- "@ecies/ciphers": ["@ecies/ciphers@0.2.6", "", { "peerDependencies": { "@noble/ciphers": "^1.0.0" } }, "sha512-patgsRPKGkhhoBjETV4XxD0En4ui5fbX0hzayqI3M8tvNMGUoUvmyYAIWwlxBc1KX5cturfqByYdj5bYGRpN9g=="],
-
"@emnapi/core": ["@emnapi/core@1.9.2", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.1", "tslib": "^2.4.0" } }, "sha512-UC+ZhH3XtczQYfOlu3lNEkdW/p4dsJ1r/bP7H8+rhao3TTTMO1ATq/4DdIi23XuGoFY+Cz0JmCbdVl0hz9jZcA=="],
"@emnapi/runtime": ["@emnapi/runtime@1.9.2", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-3U4+MIWHImeyu1wnmVygh5WlgfYDtyf0k8AbLhMFxOipihf6nrWC4syIm/SwEeec0mNSafiiNnMJwbza/Is6Lw=="],
@@ -176,86 +52,62 @@
"@esbuild-kit/esm-loader": ["@esbuild-kit/esm-loader@2.6.5", "", { "dependencies": { "@esbuild-kit/core-utils": "^3.3.2", "get-tsconfig": "^4.7.0" } }, "sha512-FxEMIkJKnodyA1OaCUoEvbYRkoZlLZ4d/eXFu9Fh8CbBBgP5EmZxrfTRyN0qpXZ4vOvqnE5YdRdcrmUUXuU+dA=="],
- "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.28.1", "", { "os": "aix", "cpu": "ppc64" }, "sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ=="],
+ "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.12", "", { "os": "aix", "cpu": "ppc64" }, "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA=="],
- "@esbuild/android-arm": ["@esbuild/android-arm@0.28.1", "", { "os": "android", "cpu": "arm" }, "sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ=="],
+ "@esbuild/android-arm": ["@esbuild/android-arm@0.25.12", "", { "os": "android", "cpu": "arm" }, "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg=="],
- "@esbuild/android-arm64": ["@esbuild/android-arm64@0.28.1", "", { "os": "android", "cpu": "arm64" }, "sha512-34EGEbCIAgosYz6goLcopX6Mo7NyGv9tfwEM2/7Ce2VcVRk568iSvniGWcUXIy7wEDR1wzolcxcriFVrWYcwBg=="],
+ "@esbuild/android-arm64": ["@esbuild/android-arm64@0.25.12", "", { "os": "android", "cpu": "arm64" }, "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg=="],
- "@esbuild/android-x64": ["@esbuild/android-x64@0.28.1", "", { "os": "android", "cpu": "x64" }, "sha512-dbwY7ltSMDWsRatcRpCnES4F+im88OCUgGZjy52shC7GqHRE/cYlxNbB4Z4UpJswpcc4Qxd2oE/ufM0p61IKng=="],
+ "@esbuild/android-x64": ["@esbuild/android-x64@0.25.12", "", { "os": "android", "cpu": "x64" }, "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg=="],
- "@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.28.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-TZbWkQY7kvTAXbXUT7uVACR5cMHsDiSz9z7ZKAX/RTq/WJEk3QyRr0wZpNhBDX+/0CtdqUIJlOiodQcta6tY3Q=="],
+ "@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.25.12", "", { "os": "darwin", "cpu": "arm64" }, "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg=="],
- "@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.28.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-zfdzgK9ACBNZLI/CyHTOx81SyNbM6YXn7rxSgX97VjyiPl9W1i4Ka4fgKECEoFCKGpvBj5qArWIGgQjOwkgskQ=="],
+ "@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.25.12", "", { "os": "darwin", "cpu": "x64" }, "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA=="],
- "@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.28.1", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-wG2EA8ENdEI0qhkSZMjfqrdY+ziCYCPMmtZjjIwOmXFjmyzEHn+UUxk5of+SYsjtfs3VpnlC7QLzSI5hY/rOAw=="],
+ "@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.25.12", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg=="],
- "@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.28.1", "", { "os": "freebsd", "cpu": "x64" }, "sha512-i7dZ9vQgnvSCzi/rYCXNgtF/U+eKZNJBzu3eTQbRgHnM7tNSizLOkRFAl3qzVc/Op/u5YkHHa4pf/3DOYHthLQ=="],
+ "@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.25.12", "", { "os": "freebsd", "cpu": "x64" }, "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ=="],
- "@esbuild/linux-arm": ["@esbuild/linux-arm@0.28.1", "", { "os": "linux", "cpu": "arm" }, "sha512-qVXBOHQS+d5Y722GwJzJUtOLlX7km3CraOaGormF1pDtPd2C/l1SHRPgjLunLGe51Sh5YYWKMFDyV4SxgMQYTQ=="],
+ "@esbuild/linux-arm": ["@esbuild/linux-arm@0.25.12", "", { "os": "linux", "cpu": "arm" }, "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw=="],
- "@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.28.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-yHs+0uc8+nvEAfAfxrWQKK5peSNzBc4PegcMO0EJ2hT71uA7vB8Ihg2e77R2P7SG5uYjPbHlLLmve4LLLRCf0g=="],
+ "@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.25.12", "", { "os": "linux", "cpu": "arm64" }, "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ=="],
- "@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.28.1", "", { "os": "linux", "cpu": "ia32" }, "sha512-d1z4ZuP0ajrfz/FhGT4vv278rX8KnPPJx8i5+AtK7TYbx9Le9F1hyzurZpkEyjkGa9dUGhQow4C1NmeGvqxN2w=="],
+ "@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.25.12", "", { "os": "linux", "cpu": "ia32" }, "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA=="],
- "@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.28.1", "", { "os": "linux", "cpu": "none" }, "sha512-M5sRjUVZrkm1OAPR3dlOYzNmN+loZKGVi1VUQGrwuqLcbR6qeAz+famMhjASeH3YVKvZz+zT1jlh/keC3Rj/lg=="],
+ "@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.25.12", "", { "os": "linux", "cpu": "none" }, "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng=="],
- "@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.28.1", "", { "os": "linux", "cpu": "none" }, "sha512-mRObBZeHh2OxcBFPWE/FjylkRgZdYuiTR3vaTozquCGOH14iP9oN4x4Ge81CoIDYQrXmIxpFumJBu5MtZpnQJQ=="],
+ "@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.25.12", "", { "os": "linux", "cpu": "none" }, "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw=="],
- "@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.28.1", "", { "os": "linux", "cpu": "ppc64" }, "sha512-slScBsMAb3GFDcdrCgLwZtPYRoH2H/youv10QiZyRjmsP48fznoveWytSgCI/R0ZcUgpc0ZhIUEx6LHts8yrfQ=="],
+ "@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.25.12", "", { "os": "linux", "cpu": "ppc64" }, "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA=="],
- "@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.28.1", "", { "os": "linux", "cpu": "none" }, "sha512-kw0owk1o0GFETUJyW0jc0G4Yzs0BHZn0JDZ8JRT088vjJYX777BAs1fDGxAC+q831qOs2DTC96mNsG2opdfyyQ=="],
+ "@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.25.12", "", { "os": "linux", "cpu": "none" }, "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w=="],
- "@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.28.1", "", { "os": "linux", "cpu": "s390x" }, "sha512-/lAIjX8aYFRByhh6L5rYtPEDRqa9de/4V/juOXcta5frjvzXO4/sqEtyytse0g3zZFuWu5cDN0MkLz2qRDD2Ag=="],
+ "@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.25.12", "", { "os": "linux", "cpu": "s390x" }, "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg=="],
- "@esbuild/linux-x64": ["@esbuild/linux-x64@0.28.1", "", { "os": "linux", "cpu": "x64" }, "sha512-u/anNYF2mmVOEDwLtnQ1wOr3EZ9sTNGLWrsYGYwHWzGA3Si84IOkHXlbWTD1NB+9/1lcnweYKO54uhxZydNzfA=="],
+ "@esbuild/linux-x64": ["@esbuild/linux-x64@0.25.12", "", { "os": "linux", "cpu": "x64" }, "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw=="],
- "@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.28.1", "", { "os": "none", "cpu": "arm64" }, "sha512-oks0DYbLwWMmaakTsCb+zL4E+aHRVLom9IJZOAthMQEPiQmydXHkziYEsGYRx0uNV/IjEKGAV941JzH02pflqw=="],
+ "@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.25.12", "", { "os": "none", "cpu": "arm64" }, "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg=="],
- "@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.28.1", "", { "os": "none", "cpu": "x64" }, "sha512-aeL6lAnN89Hz43Mlh1G8ARasbuoYvSITDEx0tHh5b7jJnHcssqgjy9Yx430GDpmCa6OyrKoS0aNRjKundRizGg=="],
+ "@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.25.12", "", { "os": "none", "cpu": "x64" }, "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ=="],
- "@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.28.1", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-MEFJe5C3R8pwXdZ5Y21oo6m7ePiS0d9pWucn99O/wvyJZChoIQKrQDxKrGeW8F5+T0okTHesAmDeiHDTIq0V/Q=="],
+ "@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.25.12", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A=="],
- "@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.28.1", "", { "os": "openbsd", "cpu": "x64" }, "sha512-i/ZLIOafE0Z8cI/XANJAixoJL/uRAoS2xOA3rb0xN+KK0K177cMAsQYkzHtBrtMXAKuAc7HGgcWiZ/sRC1Nxgw=="],
+ "@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.25.12", "", { "os": "openbsd", "cpu": "x64" }, "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw=="],
- "@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.28.1", "", { "os": "none", "cpu": "arm64" }, "sha512-ge+Z7EXFNt2BO1oAMsVpiQ8EwndV9i1xXerAeTIK7AtPs3bKFXQM7nlRxDSIUIMeueR1CNXxqztLzdNeReKBJg=="],
+ "@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.25.12", "", { "os": "none", "cpu": "arm64" }, "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg=="],
- "@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.28.1", "", { "os": "sunos", "cpu": "x64" }, "sha512-BEjgtECkL3vY+SaSQ6nzVfiALUeFxpawyp8Jmf5PtYhf1Ug40N1h/hxlhts+f1FvSvarEigdxS3BlSMI2PJLcQ=="],
+ "@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.25.12", "", { "os": "sunos", "cpu": "x64" }, "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w=="],
- "@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.28.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-lCv9eK/H6ZJWbE7bh2nw54CZ9M2nupBxJcTsdk/QQnWkdSjKGuxmmH8/GWrlT1eMmZfn4dGcCjRte397WqfQXA=="],
+ "@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.25.12", "", { "os": "win32", "cpu": "arm64" }, "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg=="],
- "@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.28.1", "", { "os": "win32", "cpu": "ia32" }, "sha512-zvb/mB2bSCoJOpoCBgYKKpX6YM6mJBlBUVUtVj41DlZJVEB6/0CKlRYxP5wWl1C1ILiCoAU5wZZ4q1P3qeS6Eg=="],
+ "@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.25.12", "", { "os": "win32", "cpu": "ia32" }, "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ=="],
- "@esbuild/win32-x64": ["@esbuild/win32-x64@0.28.1", "", { "os": "win32", "cpu": "x64" }, "sha512-bm4Mowrv+GXMlpWX++EcXw/iLyd1o3+bJkC2DkWXYVvgZCqD/bSj9ctZeAMC3cIxgjRVR2Dufaiu4YPxr5gW1A=="],
+ "@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.12", "", { "os": "win32", "cpu": "x64" }, "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA=="],
- "@floating-ui/core": ["@floating-ui/core@1.7.5", "", { "dependencies": { "@floating-ui/utils": "^0.2.11" } }, "sha512-1Ih4WTWyw0+lKyFMcBHGbb5U5FtuHJuujoyyr5zTaWS5EYMeT6Jb2AuDeftsCsEuchO+mM2ij5+q9crhydzLhQ=="],
-
- "@floating-ui/dom": ["@floating-ui/dom@1.7.6", "", { "dependencies": { "@floating-ui/core": "^1.7.5", "@floating-ui/utils": "^0.2.11" } }, "sha512-9gZSAI5XM36880PPMm//9dfiEngYoC6Am2izES1FF406YFsjvyBMmeJ2g4SAju3xWwtuynNRFL2s9hgxpLI5SQ=="],
-
- "@floating-ui/react-dom": ["@floating-ui/react-dom@2.1.8", "", { "dependencies": { "@floating-ui/dom": "^1.7.6" }, "peerDependencies": { "react": ">=16.8.0", "react-dom": ">=16.8.0" } }, "sha512-cC52bHwM/n/CxS87FH0yWdngEZrjdtLW/qVruo68qg+prK7ZQ4YGdut2GyDVpoGeAYe/h899rVeOVm6Oi40k2A=="],
-
- "@floating-ui/utils": ["@floating-ui/utils@0.2.11", "", {}, "sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg=="],
-
- "@fontsource-variable/geist": ["@fontsource-variable/geist@5.2.9", "", {}, "sha512-TP+QSBG3wxKGPE33CbMy/L0Nu3qvJ6Fy81Yc4LnQ95xH+i+cfEp8fyU8/kfV14YwszxIFPhnoMTbjL71waVpyQ=="],
-
- "@hono/node-server": ["@hono/node-server@1.19.14", "", { "peerDependencies": { "hono": "^4" } }, "sha512-GwtvgtXxnWsucXvbQXkRgqksiH2Qed37H9xHZocE5sA3N8O8O8/8FA3uclQXxXVzc9XBZuEOMK7+r02FmSpHtw=="],
-
- "@iconify/react": ["@iconify/react@6.0.2", "", { "dependencies": { "@iconify/types": "^2.0.0" }, "peerDependencies": { "react": ">=16" } }, "sha512-SMmC2sactfpJD427WJEDN6PMyznTFMhByK9yLW0gOTtnjzzbsi/Ke/XqsumsavFPwNiXs8jSiYeZTmLCLwO+Fg=="],
+ "@iconify/svelte": ["@iconify/svelte@5.2.2", "", { "dependencies": { "@iconify/types": "^2.0.0" }, "peerDependencies": { "svelte": ">5.0.0" } }, "sha512-XMXxD3nzH7yB68C3K4sNwzrJ1TBscODR0ZqCeNf0KGuEMCTSuCo0jHNDZ0o0iRXTylO41NSz/DWam/4atLG1yw=="],
"@iconify/types": ["@iconify/types@2.0.0", "", {}, "sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg=="],
- "@inquirer/ansi": ["@inquirer/ansi@2.0.7", "", {}, "sha512-3eTuUO1vH2cZm2ZKHeQxnOqlTi9EfZDGgIe3BL3I4u+rJHocr9Fz86M4fjYABPvFnQG/gGK551HqDiIcETwU6Q=="],
-
- "@inquirer/confirm": ["@inquirer/confirm@6.1.1", "", { "dependencies": { "@inquirer/core": "^11.2.1", "@inquirer/type": "^4.0.7" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-eb8DBZcz/2qHWQda4rk2JiQk5h9QV/cVHi1yjt0f69WFZMRFn0sJTye3EAP8icut8UDMjQPsaH5KbcOogefrFQ=="],
-
- "@inquirer/core": ["@inquirer/core@11.2.1", "", { "dependencies": { "@inquirer/ansi": "^2.0.7", "@inquirer/figures": "^2.0.7", "@inquirer/type": "^4.0.7", "cli-width": "^4.1.0", "fast-wrap-ansi": "^0.2.0", "mute-stream": "^3.0.0", "signal-exit": "^4.1.0" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-Qd6GJT1yVyrZZCfN8W2qKF5ApmqryXRhRKCuip8h01x2w/esJQ2XIYc6f9abMIHgKQdBfFTSOdbHRLAhuM09UA=="],
-
- "@inquirer/figures": ["@inquirer/figures@2.0.7", "", {}, "sha512-aJ8TBPOGB6f/2qziPfElISTCEd5XOYTFckA2SGjhNmiKzfK/u4ot3v0DUzGVdUnKjN10EqnnEPck36BkyfLnJw=="],
-
- "@inquirer/type": ["@inquirer/type@4.0.7", "", { "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-t28inv14nMQ1PhKpsJPY+kEs/c00qzeCOS2gTNRyTjG5d6qsVA2fItxW4hkvGZ5lvanGLdtCzVIx5dwdRpN1+g=="],
-
- "@joshwooding/vite-plugin-react-docgen-typescript": ["@joshwooding/vite-plugin-react-docgen-typescript@0.7.0", "", { "dependencies": { "glob": "^13.0.1", "react-docgen-typescript": "^2.2.2" }, "peerDependencies": { "typescript": ">= 4.3.x", "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0" }, "optionalPeers": ["typescript"] }, "sha512-qvsTEwEFefhdirGOPnu9Wp6ChfIwy2dBCRuETU3uE+4cC+PFoxMSiiEhxk4lOluA34eARHA0OxqsEUYDqRMgeQ=="],
-
"@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.13", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="],
"@jridgewell/remapping": ["@jridgewell/remapping@2.3.5", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ=="],
@@ -266,30 +118,8 @@
"@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="],
- "@modelcontextprotocol/sdk": ["@modelcontextprotocol/sdk@1.29.0", "", { "dependencies": { "@hono/node-server": "^1.19.9", "ajv": "^8.17.1", "ajv-formats": "^3.0.1", "content-type": "^1.0.5", "cors": "^2.8.5", "cross-spawn": "^7.0.5", "eventsource": "^3.0.2", "eventsource-parser": "^3.0.0", "express": "^5.2.1", "express-rate-limit": "^8.2.1", "hono": "^4.11.4", "jose": "^6.1.3", "json-schema-typed": "^8.0.2", "pkce-challenge": "^5.0.0", "raw-body": "^3.0.0", "zod": "^3.25 || ^4.0", "zod-to-json-schema": "^3.25.1" }, "peerDependencies": { "@cfworker/json-schema": "^4.1.1" }, "optionalPeers": ["@cfworker/json-schema"] }, "sha512-zo37mZA9hJWpULgkRpowewez1y6ML5GsXJPY8FI0tBBCd77HEvza4jDqRKOXgHNn867PVGCyTdzqpz0izu5ZjQ=="],
-
- "@mswjs/interceptors": ["@mswjs/interceptors@0.41.9", "", { "dependencies": { "@open-draft/deferred-promise": "^2.2.0", "@open-draft/logger": "^0.3.0", "@open-draft/until": "^2.0.0", "is-node-process": "^1.2.0", "outvariant": "^1.4.3", "strict-event-emitter": "^0.5.1" } }, "sha512-VVPPgHyQ6ShqnrmDWuxjmUIsO9gWyOZFmuOfLd9LfBGQJwZfy0gvv9pbHSJuoFNIYC7ZDX9aoFwowjcdSC4E8w=="],
-
"@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.1.5", "", { "dependencies": { "@tybys/wasm-util": "^0.10.2" }, "peerDependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1" } }, "sha512-AWPoBRJ9tsnVhor4sjO7rkni+7p+2IAEFj6cx06UgP10jkQHqay/36uRV/bFkgrh18D9vb4cr8Q0Pthskgzy+Q=="],
- "@noble/ciphers": ["@noble/ciphers@1.3.0", "", {}, "sha512-2I0gnIVPtfnMw9ee9h1dJG7tp81+8Ob3OJb3Mv37rx5L40/b0i7djjCVvGOVqc9AEIQyvyu1i6ypKdFw8R8gQw=="],
-
- "@noble/curves": ["@noble/curves@1.9.7", "", { "dependencies": { "@noble/hashes": "1.8.0" } }, "sha512-gbKGcRUYIjA3/zCCNaWDciTMFI0dCkvou3TL8Zmy5Nc7sJ47a0jtOeZoTaMxkuqRo9cRhjOdZJXegxYE5FN/xw=="],
-
- "@noble/hashes": ["@noble/hashes@1.8.0", "", {}, "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A=="],
-
- "@nodelib/fs.scandir": ["@nodelib/fs.scandir@2.1.5", "", { "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" } }, "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g=="],
-
- "@nodelib/fs.stat": ["@nodelib/fs.stat@2.0.5", "", {}, "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A=="],
-
- "@nodelib/fs.walk": ["@nodelib/fs.walk@1.2.8", "", { "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" } }, "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg=="],
-
- "@open-draft/deferred-promise": ["@open-draft/deferred-promise@3.0.0", "", {}, "sha512-XW375UK8/9SqUVNVa6M0yEy8+iTi4QN5VZ7aZuRFQmy76LRwI9wy5F4YIBU6T+eTe2/DNDo8tqu8RHlwLHM6RA=="],
-
- "@open-draft/logger": ["@open-draft/logger@0.3.0", "", { "dependencies": { "is-node-process": "^1.2.0", "outvariant": "^1.4.0" } }, "sha512-X2g45fzhxH238HKO4xbSr7+wBS8Fvw6ixhTDuvLd5mqh6bJJCFAPwU9mPDxbcrRtfxv4u5IHCEH77BmxvXmmxQ=="],
-
- "@open-draft/until": ["@open-draft/until@2.1.0", "", {}, "sha512-U69T3ItWHvLwGg5eJ0n3I62nWuE6ilHlmz7zM0npLBRvPRd7e6NYmg54vvRtP5mZG7kZqZCFVdsTWo7BPtBujg=="],
-
"@oxc-parser/binding-android-arm-eabi": ["@oxc-parser/binding-android-arm-eabi@0.127.0", "", { "os": "android", "cpu": "arm" }, "sha512-0LC7ye4hvqbIKxAzThzvswgHLFu2AURKzYLeSVvLdu2TBOYWQDmHnTqPLeA597BcUCxiLqLsS4CJ5uoI5WYWCQ=="],
"@oxc-parser/binding-android-arm64": ["@oxc-parser/binding-android-arm64@0.127.0", "", { "os": "android", "cpu": "arm64" }, "sha512-b5jtVTH6AU5CJXHNdj7Jj9IEiR9yVjjnwHzPJhGyHGPdcsZSzBCkS9GBbV33niRMvKthDwQRFRJfI4a+k4PvYg=="],
@@ -370,127 +200,7 @@
"@oxc-resolver/binding-win32-x64-msvc": ["@oxc-resolver/binding-win32-x64-msvc@11.21.3", "", { "os": "win32", "cpu": "x64" }, "sha512-H7BCt/VnS9hnmMp42eGhZ99izSCRvlnWwy/N71K1/J8QoExwY4262Z8QiEkMDtduRJrztayDxETTckmUuAVL9Q=="],
- "@playwright/test": ["@playwright/test@1.61.0", "", { "dependencies": { "playwright": "1.61.0" }, "bin": { "playwright": "cli.js" } }, "sha512-cKA5B6lpFEMyMGjxF54QihfYpB4FkEGH+qZhtArDEG+wezQAJY8Pq6C7T1SjWz+FFzt3TbyoXBQYk/0292TdJA=="],
-
- "@radix-ui/number": ["@radix-ui/number@1.1.2", "", {}, "sha512-ceTwaxc4I5IOi97DgCotl3pqiyRGvffcc0oOsE2dQYaJOFIDsDt4VWG6xEbg1QePv9QWausCEIppud/tJ1wNig=="],
-
- "@radix-ui/primitive": ["@radix-ui/primitive@1.1.4", "", {}, "sha512-7AdCK9PQyiljKoBDbN8OuctCbd/esdwZPQ8RtOE3SsyQtUpiPb+ND75q0jEhC1m1ecBI0MFNeLJvwIh9iKHRcQ=="],
-
- "@radix-ui/react-accessible-icon": ["@radix-ui/react-accessible-icon@1.1.10", "", { "dependencies": { "@radix-ui/react-visually-hidden": "1.2.6" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-TraSwZUqTcVbiDV2/RXzAXC7aeVVXchq0daPFZE7zAxYFaMzjOUggLOfQH9KFLgRizuwVKZO/crveV1eeO3/ZQ=="],
-
- "@radix-ui/react-accordion": ["@radix-ui/react-accordion@1.2.14", "", { "dependencies": { "@radix-ui/primitive": "1.1.4", "@radix-ui/react-collapsible": "1.1.14", "@radix-ui/react-collection": "1.1.10", "@radix-ui/react-compose-refs": "1.1.3", "@radix-ui/react-context": "1.1.4", "@radix-ui/react-direction": "1.1.2", "@radix-ui/react-id": "1.1.2", "@radix-ui/react-primitive": "2.1.6", "@radix-ui/react-use-controllable-state": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-iE8YB9nmTBH8zd73ofBISZ8JCzgMoMkATJr7qDwa6u5F1+7mTM81V6fa71jgZ65rpjVpecDf1vSnwIFP9Ly1zw=="],
-
- "@radix-ui/react-alert-dialog": ["@radix-ui/react-alert-dialog@1.1.17", "", { "dependencies": { "@radix-ui/primitive": "1.1.4", "@radix-ui/react-compose-refs": "1.1.3", "@radix-ui/react-context": "1.1.4", "@radix-ui/react-dialog": "1.1.17", "@radix-ui/react-primitive": "2.1.6" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-563ygGeyWPrxyVCNp7OV4rE2aIXhFPknpFyo4wbDlcyMMPZ6ySh+zC5WTvY0ZFLgPTg/QB6tA8PyDQyJ2b4cPg=="],
-
- "@radix-ui/react-arrow": ["@radix-ui/react-arrow@1.1.10", "", { "dependencies": { "@radix-ui/react-primitive": "2.1.6" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-j2VTDz1vgCsmuG0k5lBfOcM8n5JPFqZBcMryasFjHYMhwxYL5SRUV5lMSUpRdNtw3D/Sv8pzJtrlAgkssYSsQQ=="],
-
- "@radix-ui/react-aspect-ratio": ["@radix-ui/react-aspect-ratio@1.1.10", "", { "dependencies": { "@radix-ui/react-primitive": "2.1.6" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-kbI7NrqhDeuytYrq7JjAsoXczvL8wgj2tc1MyaYWm+50bMKHCHQtVWCryslx4cCpmCTTkBcwQckE4CmmGV2haQ=="],
-
- "@radix-ui/react-avatar": ["@radix-ui/react-avatar@1.2.0", "", { "dependencies": { "@radix-ui/react-context": "1.1.4", "@radix-ui/react-primitive": "2.1.6", "@radix-ui/react-use-callback-ref": "1.1.2", "@radix-ui/react-use-is-hydrated": "0.1.1", "@radix-ui/react-use-layout-effect": "1.1.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-am/CwltXtmtdtP+5FbYblYDnMa/zuKcMJP1i3/SJMDXXfj2mG+BTqLH2wucqeyyiQMursUtg/5cK+Nh2pCaSOA=="],
-
- "@radix-ui/react-checkbox": ["@radix-ui/react-checkbox@1.3.5", "", { "dependencies": { "@radix-ui/primitive": "1.1.4", "@radix-ui/react-compose-refs": "1.1.3", "@radix-ui/react-context": "1.1.4", "@radix-ui/react-presence": "1.1.6", "@radix-ui/react-primitive": "2.1.6", "@radix-ui/react-use-controllable-state": "1.2.3", "@radix-ui/react-use-previous": "1.1.2", "@radix-ui/react-use-size": "1.1.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-pREzrmNnVwGvYaBoM64huTRK7B3lrTRuwj8A9nwhPiEtMb+yudiWh6zWAqEtP0Dzd5+iBa1Ki7V1pCxV8ExMdA=="],
-
- "@radix-ui/react-collapsible": ["@radix-ui/react-collapsible@1.1.14", "", { "dependencies": { "@radix-ui/primitive": "1.1.4", "@radix-ui/react-compose-refs": "1.1.3", "@radix-ui/react-context": "1.1.4", "@radix-ui/react-id": "1.1.2", "@radix-ui/react-presence": "1.1.6", "@radix-ui/react-primitive": "2.1.6", "@radix-ui/react-use-controllable-state": "1.2.3", "@radix-ui/react-use-layout-effect": "1.1.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-9bT+FvifX1FK2Mj6UEsTdyu0cN3JaA3KdfhaBao+ONrYFy/pyOy3TU1TNw7iOk1o+0hOEq67RojlUUmoFGwxyA=="],
-
- "@radix-ui/react-collection": ["@radix-ui/react-collection@1.1.10", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.3", "@radix-ui/react-context": "1.1.4", "@radix-ui/react-primitive": "2.1.6", "@radix-ui/react-slot": "1.3.0" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-IVVz4EvBcKjrzKgof714qDnz/SzQAkLA2Emh5edlHbgcE6fNd3Un6CJLlaYcnm8N4JmAtzQgse4dOKxcD2yc9g=="],
-
- "@radix-ui/react-compose-refs": ["@radix-ui/react-compose-refs@1.1.3", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-rYOP8OMnuuPMQF1uhPVlGNcCDlkokKqGFE3JcxFViIkAXP7EvFWUliJAstrapypaBLJNHbZL6jGhbVDGTwmVhA=="],
-
- "@radix-ui/react-context": ["@radix-ui/react-context@1.1.4", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-QwH4PO5urrbO+FaGd5Aglg+YJgWTyyuZ3g/6mKvsqraLkglDdckw9JafgL5McL5VEJ6EPNduPaT3ZE9BttDAqg=="],
-
- "@radix-ui/react-context-menu": ["@radix-ui/react-context-menu@2.3.1", "", { "dependencies": { "@radix-ui/primitive": "1.1.4", "@radix-ui/react-context": "1.1.4", "@radix-ui/react-menu": "2.1.18", "@radix-ui/react-primitive": "2.1.6", "@radix-ui/react-use-controllable-state": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-XbrxS68W5dyiE4fAb96yvJwSVU5x66B20A99sD5Mk3xSWK/LqeOnx6TZnim1KieMjXS/CTFq8reOAjWxas2G8Q=="],
-
- "@radix-ui/react-dialog": ["@radix-ui/react-dialog@1.1.17", "", { "dependencies": { "@radix-ui/primitive": "1.1.4", "@radix-ui/react-compose-refs": "1.1.3", "@radix-ui/react-context": "1.1.4", "@radix-ui/react-dismissable-layer": "1.1.13", "@radix-ui/react-focus-guards": "1.1.4", "@radix-ui/react-focus-scope": "1.1.10", "@radix-ui/react-id": "1.1.2", "@radix-ui/react-portal": "1.1.12", "@radix-ui/react-presence": "1.1.6", "@radix-ui/react-primitive": "2.1.6", "@radix-ui/react-slot": "1.3.0", "@radix-ui/react-use-controllable-state": "1.2.3", "aria-hidden": "^1.2.4", "react-remove-scroll": "^2.7.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-TDTYmpdq8dI2+Xgvgj9AJ8Ghqq+Eph/TRVEdaFQPDItIY+6QSkU7MJMeevw1568Yw/2Ijz8BTphPSP2XejKphw=="],
-
- "@radix-ui/react-direction": ["@radix-ui/react-direction@1.1.2", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-C3vFhbyi4SW3PmbAi6Awpu4OzJtd0MxGurvSsYtr7p7nM8RNB3VAF3CUmnp2j50knpkrRcB7+ycVXzgLgF6yNA=="],
-
- "@radix-ui/react-dismissable-layer": ["@radix-ui/react-dismissable-layer@1.1.13", "", { "dependencies": { "@radix-ui/primitive": "1.1.4", "@radix-ui/react-compose-refs": "1.1.3", "@radix-ui/react-primitive": "2.1.6", "@radix-ui/react-use-callback-ref": "1.1.2", "@radix-ui/react-use-escape-keydown": "1.1.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-2v+zNAWWe0ySxgC0D0yeXMPQ23xZVgXZTerTz+JKlmdRj6gfTqmCcR29jb6d290DezXPGgruHWDX/vYUebtErg=="],
-
- "@radix-ui/react-dropdown-menu": ["@radix-ui/react-dropdown-menu@2.1.18", "", { "dependencies": { "@radix-ui/primitive": "1.1.4", "@radix-ui/react-compose-refs": "1.1.3", "@radix-ui/react-context": "1.1.4", "@radix-ui/react-id": "1.1.2", "@radix-ui/react-menu": "2.1.18", "@radix-ui/react-primitive": "2.1.6", "@radix-ui/react-use-controllable-state": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-PZGV82gFk0WltDRI//SsG28ZIjlo9ANTmoNYg0jLNzXXiDsAy5PkOOYQaVD1pPxY6t7gxffb1QMD6qaUvsBZdw=="],
-
- "@radix-ui/react-focus-guards": ["@radix-ui/react-focus-guards@1.1.4", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-cot/aB/mOm0IYVYTTmQcEEK1M48lZWi8FlYe5nDPQQ8NYZUlXEFgncJ9p2Kzer3RKSrY7cTTpEMLZKNo9QoP5Q=="],
-
- "@radix-ui/react-focus-scope": ["@radix-ui/react-focus-scope@1.1.10", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.3", "@radix-ui/react-primitive": "2.1.6", "@radix-ui/react-use-callback-ref": "1.1.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-Fas/lXQqhVvqwAb64s5RFeHiHYElZ6SUQbZaNd6EkfhP/Al7wTIQ9WIR4QVX475tlu5yFCEdDcJH6/UwsZjMWw=="],
-
- "@radix-ui/react-form": ["@radix-ui/react-form@0.1.10", "", { "dependencies": { "@radix-ui/primitive": "1.1.4", "@radix-ui/react-compose-refs": "1.1.3", "@radix-ui/react-context": "1.1.4", "@radix-ui/react-id": "1.1.2", "@radix-ui/react-label": "2.1.10", "@radix-ui/react-primitive": "2.1.6" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-1NfuvctVtX4sU3Mmq/IdrR8UunxiCMiVg3A5UENKhFzxUBeOyaQQ+lmaQaV7Tc8cqvBKsJL3/KGBsixK0D8WFg=="],
-
- "@radix-ui/react-hover-card": ["@radix-ui/react-hover-card@1.1.17", "", { "dependencies": { "@radix-ui/primitive": "1.1.4", "@radix-ui/react-compose-refs": "1.1.3", "@radix-ui/react-context": "1.1.4", "@radix-ui/react-dismissable-layer": "1.1.13", "@radix-ui/react-popper": "1.3.1", "@radix-ui/react-portal": "1.1.12", "@radix-ui/react-presence": "1.1.6", "@radix-ui/react-primitive": "2.1.6", "@radix-ui/react-use-controllable-state": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-GjZQIEANVkuuWeztlKz6QEHe31ZX2iDfHzcTMCQVZXC0JyQrgfKWSC+LOOEw6aVV64zyjzobIzSA4AU4eKWrHA=="],
-
- "@radix-ui/react-id": ["@radix-ui/react-id@1.1.2", "", { "dependencies": { "@radix-ui/react-use-layout-effect": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-orBC88futVpqCmhX1p4cvquNHsELQ+w+vBJnuj3ftETI5bJb0bZn3Tqu3SWN2IOcPycTnMGnhwoermvISt72sA=="],
-
- "@radix-ui/react-label": ["@radix-ui/react-label@2.1.10", "", { "dependencies": { "@radix-ui/react-primitive": "2.1.6" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-ib0zvq2ZsAqKm5tRnqGJn3vOxSgIts5ToxsXT0q1S/GfLD1Zj7UOEnkw8u2w6sRmn47djpQWuSU1DCL1R29/yw=="],
-
- "@radix-ui/react-menu": ["@radix-ui/react-menu@2.1.18", "", { "dependencies": { "@radix-ui/primitive": "1.1.4", "@radix-ui/react-collection": "1.1.10", "@radix-ui/react-compose-refs": "1.1.3", "@radix-ui/react-context": "1.1.4", "@radix-ui/react-direction": "1.1.2", "@radix-ui/react-dismissable-layer": "1.1.13", "@radix-ui/react-focus-guards": "1.1.4", "@radix-ui/react-focus-scope": "1.1.10", "@radix-ui/react-id": "1.1.2", "@radix-ui/react-popper": "1.3.1", "@radix-ui/react-portal": "1.1.12", "@radix-ui/react-presence": "1.1.6", "@radix-ui/react-primitive": "2.1.6", "@radix-ui/react-roving-focus": "1.1.13", "@radix-ui/react-slot": "1.3.0", "@radix-ui/react-use-callback-ref": "1.1.2", "aria-hidden": "^1.2.4", "react-remove-scroll": "^2.7.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-lj8Rxjtn6zJq1oSbE/uDtAwCbB9BnxgHD+8MwJMuTh6u1dPamYhW9iuELr/Z8d0D/UysFblYYHeBPwi7T4k0YQ=="],
-
- "@radix-ui/react-menubar": ["@radix-ui/react-menubar@1.1.18", "", { "dependencies": { "@radix-ui/primitive": "1.1.4", "@radix-ui/react-collection": "1.1.10", "@radix-ui/react-compose-refs": "1.1.3", "@radix-ui/react-context": "1.1.4", "@radix-ui/react-direction": "1.1.2", "@radix-ui/react-id": "1.1.2", "@radix-ui/react-menu": "2.1.18", "@radix-ui/react-primitive": "2.1.6", "@radix-ui/react-roving-focus": "1.1.13", "@radix-ui/react-use-controllable-state": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-hX7EGx/oFq6DPY27GQuP/2wP48GHf5LG6r06VgNJlG+znmDS8OfopZcRcGly3L4lsB9FqpmLx6JQSE9P3BUpyw=="],
-
- "@radix-ui/react-navigation-menu": ["@radix-ui/react-navigation-menu@1.2.16", "", { "dependencies": { "@radix-ui/primitive": "1.1.4", "@radix-ui/react-collection": "1.1.10", "@radix-ui/react-compose-refs": "1.1.3", "@radix-ui/react-context": "1.1.4", "@radix-ui/react-direction": "1.1.2", "@radix-ui/react-dismissable-layer": "1.1.13", "@radix-ui/react-id": "1.1.2", "@radix-ui/react-presence": "1.1.6", "@radix-ui/react-primitive": "2.1.6", "@radix-ui/react-use-callback-ref": "1.1.2", "@radix-ui/react-use-controllable-state": "1.2.3", "@radix-ui/react-use-layout-effect": "1.1.2", "@radix-ui/react-use-previous": "1.1.2", "@radix-ui/react-visually-hidden": "1.2.6" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-nJ0SkrSQgudyYhMiYeHA1ayLVuduEJCFLan1RZZN7c9kqzzCFLaU9kuy81uNtqzweM9YaQPgWzxi9MwQ9jZ04g=="],
-
- "@radix-ui/react-one-time-password-field": ["@radix-ui/react-one-time-password-field@0.1.10", "", { "dependencies": { "@radix-ui/number": "1.1.2", "@radix-ui/primitive": "1.1.4", "@radix-ui/react-collection": "1.1.10", "@radix-ui/react-compose-refs": "1.1.3", "@radix-ui/react-context": "1.1.4", "@radix-ui/react-direction": "1.1.2", "@radix-ui/react-primitive": "2.1.6", "@radix-ui/react-roving-focus": "1.1.13", "@radix-ui/react-use-controllable-state": "1.2.3", "@radix-ui/react-use-effect-event": "0.0.3", "@radix-ui/react-use-is-hydrated": "0.1.1", "@radix-ui/react-use-layout-effect": "1.1.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-GHkcJ+WVj91At+OvUVTD4R3W0/wxw9t/sG5xFUBYXaCbtWiooZX5Md376QjJqgH4VsVyXrbVNHO2O4NYcmjfVg=="],
-
- "@radix-ui/react-password-toggle-field": ["@radix-ui/react-password-toggle-field@0.1.5", "", { "dependencies": { "@radix-ui/primitive": "1.1.4", "@radix-ui/react-compose-refs": "1.1.3", "@radix-ui/react-context": "1.1.4", "@radix-ui/react-id": "1.1.2", "@radix-ui/react-primitive": "2.1.6", "@radix-ui/react-use-controllable-state": "1.2.3", "@radix-ui/react-use-effect-event": "0.0.3", "@radix-ui/react-use-is-hydrated": "0.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-fVuA82u0b/fClpbEJv8yp1nU9eSvoSEOERsU/hhf3FXGPIvkmE7oEaHEu8poowoXO39/Va7zq2E0TUcYr1dBRg=="],
-
- "@radix-ui/react-popover": ["@radix-ui/react-popover@1.1.17", "", { "dependencies": { "@radix-ui/primitive": "1.1.4", "@radix-ui/react-compose-refs": "1.1.3", "@radix-ui/react-context": "1.1.4", "@radix-ui/react-dismissable-layer": "1.1.13", "@radix-ui/react-focus-guards": "1.1.4", "@radix-ui/react-focus-scope": "1.1.10", "@radix-ui/react-id": "1.1.2", "@radix-ui/react-popper": "1.3.1", "@radix-ui/react-portal": "1.1.12", "@radix-ui/react-presence": "1.1.6", "@radix-ui/react-primitive": "2.1.6", "@radix-ui/react-slot": "1.3.0", "@radix-ui/react-use-controllable-state": "1.2.3", "aria-hidden": "^1.2.4", "react-remove-scroll": "^2.7.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-/YSAOdJ7YJvdn7bn5sdSx2egW+SKY+u7O5RyAVs94Ymrg2fg5QTSFPMRkzvhGyFuE4/qsmPBdrwYoZMZh/4f+g=="],
-
- "@radix-ui/react-popper": ["@radix-ui/react-popper@1.3.1", "", { "dependencies": { "@floating-ui/react-dom": "^2.0.0", "@radix-ui/react-arrow": "1.1.10", "@radix-ui/react-compose-refs": "1.1.3", "@radix-ui/react-context": "1.1.4", "@radix-ui/react-primitive": "2.1.6", "@radix-ui/react-use-callback-ref": "1.1.2", "@radix-ui/react-use-layout-effect": "1.1.2", "@radix-ui/react-use-rect": "1.1.2", "@radix-ui/react-use-size": "1.1.2", "@radix-ui/rect": "1.1.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-bhnq/0DEPTi2lsOD3J5rTL65qUKHbKbhqHsmN9TMiclSXpipi651ooUKPPp6G5lF/WiHBdn1s0Wuqsn+myVAvw=="],
-
- "@radix-ui/react-portal": ["@radix-ui/react-portal@1.1.12", "", { "dependencies": { "@radix-ui/react-primitive": "2.1.6", "@radix-ui/react-use-layout-effect": "1.1.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-m309havGzsjLHHaIX50G5PlvRs3xkgPCsGk/5PTvYm8D5q33yG0J7w/712PTOhid7NTaFETtnSXjngHQavvhVw=="],
-
- "@radix-ui/react-presence": ["@radix-ui/react-presence@1.1.6", "", { "dependencies": { "@radix-ui/react-use-layout-effect": "1.1.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-zdTk4PlUO0E18HnZ3wYbW0KkJJxWCdiNYp6g6X1PtONFhxVkg01vliTJAmwIszU6mHiyBOoW9P0rAugl5/hULQ=="],
-
- "@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.6", "", { "dependencies": { "@radix-ui/react-slot": "1.3.0" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-wetd0QI77DbvrPpTAvH1SqOxsYF2wZe5TNxqwOd5Ty4XDpV3dpV0s8K/1MGMJBeY5o7lg8ub5VIt1Ub+yVen6g=="],
-
- "@radix-ui/react-progress": ["@radix-ui/react-progress@1.1.10", "", { "dependencies": { "@radix-ui/react-context": "1.1.4", "@radix-ui/react-primitive": "2.1.6" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-JYzEg60lk79PwKM27WZyKd7PW8O4OM5jOaFfRPfOyeXmMw7tLJh5kSj+CEjVTehszuwml/AdCzPGMXBTGf4BBw=="],
-
- "@radix-ui/react-radio-group": ["@radix-ui/react-radio-group@1.4.1", "", { "dependencies": { "@radix-ui/primitive": "1.1.4", "@radix-ui/react-compose-refs": "1.1.3", "@radix-ui/react-context": "1.1.4", "@radix-ui/react-direction": "1.1.2", "@radix-ui/react-presence": "1.1.6", "@radix-ui/react-primitive": "2.1.6", "@radix-ui/react-roving-focus": "1.1.13", "@radix-ui/react-use-controllable-state": "1.2.3", "@radix-ui/react-use-previous": "1.1.2", "@radix-ui/react-use-size": "1.1.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-/SSxZdKEo2Eo29FFRKd06EfFDYp8HryKg0WYg7QLXaydPzl52YfSvCH2a3QDBRdtcuwACroJT8UVjQVgOJ7P9A=="],
-
- "@radix-ui/react-roving-focus": ["@radix-ui/react-roving-focus@1.1.13", "", { "dependencies": { "@radix-ui/primitive": "1.1.4", "@radix-ui/react-collection": "1.1.10", "@radix-ui/react-compose-refs": "1.1.3", "@radix-ui/react-context": "1.1.4", "@radix-ui/react-direction": "1.1.2", "@radix-ui/react-id": "1.1.2", "@radix-ui/react-primitive": "2.1.6", "@radix-ui/react-use-callback-ref": "1.1.2", "@radix-ui/react-use-controllable-state": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-9gkwneI0guf8JDmrFxPjJF6Ozzgioyw+/lonYNCwefS9ZHA05er0BVHiXr+LbWGHxUfczvMY6G1oiZZi1VzjRw=="],
-
- "@radix-ui/react-scroll-area": ["@radix-ui/react-scroll-area@1.2.12", "", { "dependencies": { "@radix-ui/number": "1.1.2", "@radix-ui/primitive": "1.1.4", "@radix-ui/react-compose-refs": "1.1.3", "@radix-ui/react-context": "1.1.4", "@radix-ui/react-direction": "1.1.2", "@radix-ui/react-presence": "1.1.6", "@radix-ui/react-primitive": "2.1.6", "@radix-ui/react-use-callback-ref": "1.1.2", "@radix-ui/react-use-layout-effect": "1.1.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-xuafVzQiTCLsyEjakowTdG3OgTXsmO7IdCiO77otIa+z44xoLNs9Do5eg7POFumIOCjtG6djfm6RKUKpUa/csA=="],
-
- "@radix-ui/react-select": ["@radix-ui/react-select@2.3.1", "", { "dependencies": { "@radix-ui/number": "1.1.2", "@radix-ui/primitive": "1.1.4", "@radix-ui/react-collection": "1.1.10", "@radix-ui/react-compose-refs": "1.1.3", "@radix-ui/react-context": "1.1.4", "@radix-ui/react-direction": "1.1.2", "@radix-ui/react-dismissable-layer": "1.1.13", "@radix-ui/react-focus-guards": "1.1.4", "@radix-ui/react-focus-scope": "1.1.10", "@radix-ui/react-id": "1.1.2", "@radix-ui/react-popper": "1.3.1", "@radix-ui/react-portal": "1.1.12", "@radix-ui/react-presence": "1.1.6", "@radix-ui/react-primitive": "2.1.6", "@radix-ui/react-slot": "1.3.0", "@radix-ui/react-use-callback-ref": "1.1.2", "@radix-ui/react-use-controllable-state": "1.2.3", "@radix-ui/react-use-layout-effect": "1.1.2", "@radix-ui/react-use-previous": "1.1.2", "@radix-ui/react-visually-hidden": "1.2.6", "aria-hidden": "^1.2.4", "react-remove-scroll": "^2.7.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-w6eDvY78LE9ZUiNnXCA1QVK8RYN7k9galFv09kjVydJqBAgHd7Y9A6h0UJ/6DCZNGZMZrB2ohcSW1Bo9d8+wWA=="],
-
- "@radix-ui/react-separator": ["@radix-ui/react-separator@1.1.10", "", { "dependencies": { "@radix-ui/react-primitive": "2.1.6" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-Y6K6jLQCVfCnTL2MEtGxDLffkhNfEfHsEg3Wa8JU+IWdn3EWbLXd3OuOfQRN7p/W/cUce1WyTk3QeuAoDBzN9g=="],
-
- "@radix-ui/react-slider": ["@radix-ui/react-slider@1.4.1", "", { "dependencies": { "@radix-ui/number": "1.1.2", "@radix-ui/primitive": "1.1.4", "@radix-ui/react-collection": "1.1.10", "@radix-ui/react-compose-refs": "1.1.3", "@radix-ui/react-context": "1.1.4", "@radix-ui/react-direction": "1.1.2", "@radix-ui/react-primitive": "2.1.6", "@radix-ui/react-use-controllable-state": "1.2.3", "@radix-ui/react-use-layout-effect": "1.1.2", "@radix-ui/react-use-previous": "1.1.2", "@radix-ui/react-use-size": "1.1.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-r91WSpQucNGFKAIxT8FT0H0zyjd5tJlqObLp7LOMV4z49KoDCwjy01w3vDOU4e1wxhF9IgjYco7SB6byOW7Buw=="],
-
- "@radix-ui/react-slot": ["@radix-ui/react-slot@1.3.0", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.3" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-MojKku4U/miO8Av4Dkb+ctMAQx7JmY96LmtDQlAarCRtd7rN52QCSzBF+XAvr5S6coSVj9HEPBgHAHKEJVk/WA=="],
-
- "@radix-ui/react-switch": ["@radix-ui/react-switch@1.3.1", "", { "dependencies": { "@radix-ui/primitive": "1.1.4", "@radix-ui/react-compose-refs": "1.1.3", "@radix-ui/react-context": "1.1.4", "@radix-ui/react-primitive": "2.1.6", "@radix-ui/react-use-controllable-state": "1.2.3", "@radix-ui/react-use-previous": "1.1.2", "@radix-ui/react-use-size": "1.1.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-55bQtCnOB0BohomSHi6qvQXpJEEqUGDm6hRrM0Bph5OXwhSegqkd8IqgBAQkM1IlgUlWZIxpxRcpOEfRIgimyw=="],
-
- "@radix-ui/react-tabs": ["@radix-ui/react-tabs@1.1.15", "", { "dependencies": { "@radix-ui/primitive": "1.1.4", "@radix-ui/react-context": "1.1.4", "@radix-ui/react-direction": "1.1.2", "@radix-ui/react-id": "1.1.2", "@radix-ui/react-presence": "1.1.6", "@radix-ui/react-primitive": "2.1.6", "@radix-ui/react-roving-focus": "1.1.13", "@radix-ui/react-use-controllable-state": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-kxc9gI6/HfcU4nfMMVS3AmQK414kbU1IE6UCJmMmxjhO3cRPXOyYnmvyKD+ODt7q56nRq9l7Wovi6uaGwKgMlg=="],
-
- "@radix-ui/react-toast": ["@radix-ui/react-toast@1.2.17", "", { "dependencies": { "@radix-ui/primitive": "1.1.4", "@radix-ui/react-collection": "1.1.10", "@radix-ui/react-compose-refs": "1.1.3", "@radix-ui/react-context": "1.1.4", "@radix-ui/react-dismissable-layer": "1.1.13", "@radix-ui/react-portal": "1.1.12", "@radix-ui/react-presence": "1.1.6", "@radix-ui/react-primitive": "2.1.6", "@radix-ui/react-use-callback-ref": "1.1.2", "@radix-ui/react-use-controllable-state": "1.2.3", "@radix-ui/react-use-layout-effect": "1.1.2", "@radix-ui/react-visually-hidden": "1.2.6" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-uL4kyyWy000pPL43fGGCV5qT6ZchCWEQZOSlkYiPwPt8Hy1iW38RjeptIvz1/SZesrW6Vn58Ct3sV7tfEfiAbw=="],
-
- "@radix-ui/react-toggle": ["@radix-ui/react-toggle@1.1.12", "", { "dependencies": { "@radix-ui/primitive": "1.1.4", "@radix-ui/react-primitive": "2.1.6", "@radix-ui/react-use-controllable-state": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-AsAVsYNZIlRBsci7BhE+QyQeKd1h6TffJYt+lF0QQkd5OpQ3klfIByPsCb4G0h/Fq6PJwh1FYNluzBFYzhk4+w=="],
-
- "@radix-ui/react-toggle-group": ["@radix-ui/react-toggle-group@1.1.13", "", { "dependencies": { "@radix-ui/primitive": "1.1.4", "@radix-ui/react-context": "1.1.4", "@radix-ui/react-direction": "1.1.2", "@radix-ui/react-primitive": "2.1.6", "@radix-ui/react-roving-focus": "1.1.13", "@radix-ui/react-toggle": "1.1.12", "@radix-ui/react-use-controllable-state": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-Xb9PLtlvU66F36LiKba6dFswu6V2mDkgidO4fNSbQHQwmZ9ObxMIO17MN/LJ4aWJecVuSVLAHPZjyeMzJrgeiA=="],
-
- "@radix-ui/react-toolbar": ["@radix-ui/react-toolbar@1.1.13", "", { "dependencies": { "@radix-ui/primitive": "1.1.4", "@radix-ui/react-context": "1.1.4", "@radix-ui/react-direction": "1.1.2", "@radix-ui/react-primitive": "2.1.6", "@radix-ui/react-roving-focus": "1.1.13", "@radix-ui/react-separator": "1.1.10", "@radix-ui/react-toggle-group": "1.1.13" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-Za1l4f6fzTkGgz/iynAMN8iaqiKff2wm2/QwiLmHPtDQreWEBrvSimgQFIekxMUdRPhILM7xdIXxuS/o/DGZag=="],
-
- "@radix-ui/react-tooltip": ["@radix-ui/react-tooltip@1.2.10", "", { "dependencies": { "@radix-ui/primitive": "1.1.4", "@radix-ui/react-compose-refs": "1.1.3", "@radix-ui/react-context": "1.1.4", "@radix-ui/react-dismissable-layer": "1.1.13", "@radix-ui/react-id": "1.1.2", "@radix-ui/react-popper": "1.3.1", "@radix-ui/react-portal": "1.1.12", "@radix-ui/react-presence": "1.1.6", "@radix-ui/react-primitive": "2.1.6", "@radix-ui/react-slot": "1.3.0", "@radix-ui/react-use-controllable-state": "1.2.3", "@radix-ui/react-visually-hidden": "1.2.6" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-NlNe8D0dWEpVfXFli90IO6X07Josx/b1iu98tDnx9Xv0HT4wLIL+m2VOheMHhK7qbp2HoTBqALEFzGyZs/levw=="],
-
- "@radix-ui/react-use-callback-ref": ["@radix-ui/react-use-callback-ref@1.1.2", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-xCso9j1/u8sEgP1RNHjFrXJLApL8LiqOkI1R4ywuN00rxWdYg4oQXuwKLS3i0j5NWLromUD27/4nlxj2UFVvIw=="],
-
- "@radix-ui/react-use-controllable-state": ["@radix-ui/react-use-controllable-state@1.2.3", "", { "dependencies": { "@radix-ui/react-use-effect-event": "0.0.3", "@radix-ui/react-use-layout-effect": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-PLzC90MS+ReootmjC597dvopoelpZ8Q61HJkDXZSExitIq7PL55vHNnesAHwguHK0aPfBnpdNzQtv1uliaqQrA=="],
-
- "@radix-ui/react-use-effect-event": ["@radix-ui/react-use-effect-event@0.0.3", "", { "dependencies": { "@radix-ui/react-use-layout-effect": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-6c8ZqvPTWILEKnyVkP53EGRCcpnJiKTC21sS/6R1GF5xKyHJJWQEPfkqlcgUkdRQivd6tb23abUwe4ngWmY0JA=="],
-
- "@radix-ui/react-use-escape-keydown": ["@radix-ui/react-use-escape-keydown@1.1.2", "", { "dependencies": { "@radix-ui/react-use-callback-ref": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-2uVLvLjgO7NZCWw01/FdqRwmA42J0BcjPMUCA+koFEOAb+zjqIP7SiFz/7zWPrKnVmSqr76Omq2ALyCuX4dhLw=="],
-
- "@radix-ui/react-use-is-hydrated": ["@radix-ui/react-use-is-hydrated@0.1.1", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-qwOiz4Tjo8CNnrOLAYUMXeZwDzXgXpvK4TKQPmWLECM9XoWvA6+0Z2/7Ag3A4ivjS4ovbLJPbskkxioFyBhr8A=="],
-
- "@radix-ui/react-use-layout-effect": ["@radix-ui/react-use-layout-effect@1.1.2", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-jrBWOxZITuGcnjRCM2t2U5ZPkCLxD+Ym6DjfssS5haTj2iiak/DOb64JeN6OdLfLgptb6/e2kKR+ZuTrGoZTPA=="],
-
- "@radix-ui/react-use-previous": ["@radix-ui/react-use-previous@1.1.2", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-IGBQPtRFdhN6MQ8dbegVmBq1LVZluya3F1jWY+puIcQC3MHctRwTDSBWCkL/3ZcnMJLTMJ++Z+ktmvg0F89iCw=="],
-
- "@radix-ui/react-use-rect": ["@radix-ui/react-use-rect@1.1.2", "", { "dependencies": { "@radix-ui/rect": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-d8a+bBY/FxikNPlgJJoaBHZX+zKVbWHYJGTLnLvveQgFSTntkGdEKv3JDtHrMS0DNYpllz2nRsTLGLKYttbpmw=="],
-
- "@radix-ui/react-use-size": ["@radix-ui/react-use-size@1.1.2", "", { "dependencies": { "@radix-ui/react-use-layout-effect": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-giWQp+4mxjBPt4KZ0MmyuykFNWfbDxKt4x+fPkRYmgRFJSbCZFzUglvMb/Kjn38tm10YP4ufiQZDx3zna4LU6w=="],
-
- "@radix-ui/react-visually-hidden": ["@radix-ui/react-visually-hidden@1.2.6", "", { "dependencies": { "@radix-ui/react-primitive": "2.1.6" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-jCE0WljWifTI4niIMCll06kGpsJTAPiZVU9H4WR1N6qW7At9ystHbN7dDB+we2xH535roFHj7qKS+RGj0FMDWQ=="],
-
- "@radix-ui/rect": ["@radix-ui/rect@1.1.2", "", {}, "sha512-xnXE7wG13PI+cxieVssYXlQJuYVRhH9NBoxt3KNwzghDIA69GMm7d4wXRouHIYjE+KvS6U/MsMO73NdS2MH9ZA=="],
+ "@polka/url": ["@polka/url@1.0.0-next.29", "", {}, "sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww=="],
"@rolldown/binding-android-arm64": ["@rolldown/binding-android-arm64@1.0.3", "", { "os": "android", "cpu": "arm64" }, "sha512-454rs7jHngixp/NMxd5srYD57OnzSlZ/eFTETjORQHLwJG1lRtmNOJcBerZlfu4GjKqeq8aCCIQrMdHyhI51Hw=="],
@@ -524,6 +234,12 @@
"@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.1", "", {}, "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw=="],
+ "@rollup/plugin-commonjs": ["@rollup/plugin-commonjs@29.0.3", "", { "dependencies": { "@rollup/pluginutils": "^5.0.1", "commondir": "^1.0.1", "estree-walker": "^2.0.2", "fdir": "^6.2.0", "is-reference": "1.2.1", "magic-string": "^0.30.3", "picomatch": "^4.0.2" }, "peerDependencies": { "rollup": "^2.68.0||^3.0.0||^4.0.0" }, "optionalPeers": ["rollup"] }, "sha512-ZaOxZceP7SOUW7Lqw5IRVweSQYWaeIPnXIGLiB690EBA3FGJTO40EEr2L5yZplJWsgTCogILRSpcAe7+U0Otdg=="],
+
+ "@rollup/plugin-json": ["@rollup/plugin-json@6.1.0", "", { "dependencies": { "@rollup/pluginutils": "^5.1.0" }, "peerDependencies": { "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" }, "optionalPeers": ["rollup"] }, "sha512-EGI2te5ENk1coGeADSIwZ7G2Q8CJS2sF120T7jLw4xFw9n7wIOXHo+kIYRAoVpJAN+kmqZSoO3Fp4JtoNF4ReA=="],
+
+ "@rollup/plugin-node-resolve": ["@rollup/plugin-node-resolve@16.0.3", "", { "dependencies": { "@rollup/pluginutils": "^5.0.1", "@types/resolve": "1.20.2", "deepmerge": "^4.2.2", "is-module": "^1.0.0", "resolve": "^1.22.1" }, "peerDependencies": { "rollup": "^2.78.0||^3.0.0||^4.0.0" }, "optionalPeers": ["rollup"] }, "sha512-lUYM3UBGuM93CnMPG1YocWu7X802BrNF3jW2zny5gQyLQgRFJhV1Sq0Zi74+dh/6NBx1DxFC4b4GXg9wUCG5Qg=="],
+
"@rollup/pluginutils": ["@rollup/pluginutils@5.4.0", "", { "dependencies": { "@types/estree": "^1.0.0", "estree-walker": "^2.0.2", "picomatch": "^4.0.2" }, "peerDependencies": { "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" }, "optionalPeers": ["rollup"] }, "sha512-MfPp06CjRLfXQ3wY0R8vJDYBy/MvVcc9OulEfR0B8Iv9ko+GCNaRZ+EpJYFl27LhKsZK0o420sYCRHCjfCgeUg=="],
"@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.62.0", "", { "os": "android", "cpu": "arm" }, "sha512-IPIQ55ythEHkfEd9jMEi32OQ7SxURsGA43JI22lj01OLZNt2NUbJX8YUHxkVWyQ6daHPNn0truF5nSj3DQp6YQ=="],
@@ -576,61 +292,41 @@
"@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.62.0", "", { "os": "win32", "cpu": "x64" }, "sha512-5L+T1fMX4RIEBoZzT0+sQ0PhTS36NULFmMXtl1TZo44TMAROIMHbZufSOjVWt/Y622BtxgxtaNOokbTDvfsrZA=="],
- "@sec-ant/readable-stream": ["@sec-ant/readable-stream@0.4.1", "", {}, "sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg=="],
-
"@sinclair/typebox": ["@sinclair/typebox@0.34.49", "", {}, "sha512-brySQQs7Jtn0joV8Xh9ZV/hZb9Ozb0pmazDIASBkYKCjXrXU3mpcFahmK/z4YDhGkQvP9mWJbVyahdtU5wQA+A=="],
- "@sindresorhus/merge-streams": ["@sindresorhus/merge-streams@4.0.0", "", {}, "sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ=="],
-
"@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],
"@storybook/addon-a11y": ["@storybook/addon-a11y@10.4.6", "", { "dependencies": { "@storybook/global": "^5.0.0", "axe-core": "^4.2.0" }, "peerDependencies": { "storybook": "^10.4.6" } }, "sha512-XCJy+f0DFOiCgUU9knRDlLDxVFI+AAQ3/wE/NF85zB9iDPPS2DwkSN+mas3zDgHt66zhN8Cq3/UiyCDUweV9Zw=="],
+ "@storybook/addon-svelte-csf": ["@storybook/addon-svelte-csf@5.1.2", "", { "dependencies": { "@storybook/csf": "^0.1.13", "dedent": "^1.5.3", "es-toolkit": "^1.26.1", "esrap": "^1.2.2", "magic-string": "^0.30.12", "svelte-ast-print": "^0.4.0", "zimmerframe": "^1.1.2" }, "peerDependencies": { "@storybook/svelte": "^0.0.0-0 || ^8.2.0 || ^9.0.0 || ^9.1.0-0 || ^10.0.0-0 || ^10.1.0-0 || ^10.2.0-0 || ^10.3.0-0 || ^10.4.0-0", "@sveltejs/vite-plugin-svelte": "^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0", "storybook": "^0.0.0-0 || ^8.2.0 || ^9.0.0 || ^9.1.0-0 || ^10.0.0-0 || ^10.1.0-0 || ^10.2.0-0 || ^10.3.0-0 || ^10.4.0-0", "svelte": "^5.0.0", "vite": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-NpImknEb48J7yr/ArTYpvhDSvGUrgm5Nuybu9PCicjSKTACsXX7cln2R19572ORtns399RTE+t20BBOKxSPm2g=="],
+
"@storybook/addon-vitest": ["@storybook/addon-vitest@10.4.6", "", { "dependencies": { "@storybook/global": "^5.0.0", "@storybook/icons": "^2.0.2" }, "peerDependencies": { "@vitest/browser": "^3.0.0 || ^4.0.0", "@vitest/browser-playwright": "^4.0.0", "@vitest/runner": "^3.0.0 || ^4.0.0", "storybook": "^10.4.6", "vitest": "^3.0.0 || ^4.0.0" }, "optionalPeers": ["@vitest/browser", "@vitest/browser-playwright", "@vitest/runner", "vitest"] }, "sha512-VvskHge0GZy86LG6kcY5Ww34z8rDV8JBxqSdUpcJVsWfIvyX6MfAbqI76LlereSyBIJGZJZsqaLwRXsQoVY+0Q=="],
"@storybook/builder-vite": ["@storybook/builder-vite@10.4.6", "", { "dependencies": { "@storybook/csf-plugin": "10.4.6", "ts-dedent": "^2.0.0" }, "peerDependencies": { "storybook": "^10.4.6", "vite": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-BHBtD81HiXUiDQz/CaFynLtWmm7AFUQn8VnXuHipZ8KlnUANopa4yqdVuy/Gwz8ub254uFI5NMZsW/KlgWNgNg=="],
+ "@storybook/csf": ["@storybook/csf@0.1.13", "", { "dependencies": { "type-fest": "^2.19.0" } }, "sha512-7xOOwCLGB3ebM87eemep89MYRFTko+D8qE7EdAAq74lgdqRR5cOUtYWJLjO2dLtP94nqoOdHJo6MdLLKzg412Q=="],
+
"@storybook/csf-plugin": ["@storybook/csf-plugin@10.4.6", "", { "dependencies": { "unplugin": "^2.3.5" }, "peerDependencies": { "esbuild": "*", "rollup": "*", "storybook": "^10.4.6", "vite": "*", "webpack": "*" }, "optionalPeers": ["esbuild", "rollup", "vite", "webpack"] }, "sha512-NILLxDqpA/JR/AazGWpsz+4fadJwRU4uhHephGtYpVOWnQA/DkJfKT6zpcJVq8+QA8A2zKMLX3GVKsXIrxjuDA=="],
"@storybook/global": ["@storybook/global@5.0.0", "", {}, "sha512-FcOqPAXACP0I3oJ/ws6/rrPT9WGhu915Cg8D02a9YxLo0DE9zI+a9A5gRGvmQ09fiWPukqI8ZAEoQEdWUKMQdQ=="],
"@storybook/icons": ["@storybook/icons@2.0.2", "", { "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-KZBCpXsshAIjczYNXR/rlxEtCUX/eAbpFNwKi8bcOomrLA4t/SyPz5RF+lVPO2oZBUE4sAkt43mfJUevQDSEEw=="],
- "@storybook/react": ["@storybook/react@10.4.6", "", { "dependencies": { "@storybook/global": "^5.0.0", "@storybook/react-dom-shim": "10.4.6", "react-docgen": "^8.0.2", "react-docgen-typescript": "^2.2.2" }, "peerDependencies": { "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "@types/react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "storybook": "^10.4.6", "typescript": ">= 4.9.x" }, "optionalPeers": ["@types/react", "@types/react-dom", "typescript"] }, "sha512-9Y7YecrVFe1/01KYjfOLxVqTg2Aq+IO6TEv6sC2U0PfD0AWCSCmQ91QqgBpN/XW4aFFWoiZNinyXMUlU8zxy2w=="],
+ "@storybook/svelte": ["@storybook/svelte@10.4.6", "", { "dependencies": { "ts-dedent": "^2.0.0", "type-fest": "^5.6.0" }, "peerDependencies": { "storybook": "^10.4.6", "svelte": "^5.0.0" } }, "sha512-U73tDy/2vgY83Zjjy7Q5Lz0ElpZZjGQHZHZd1rrD0gv1+Zas/7aTnRjRqIUrBNqHBx1S8VA5Tzp5YI0FkM1IOw=="],
- "@storybook/react-dom-shim": ["@storybook/react-dom-shim@10.4.6", "", { "peerDependencies": { "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "@types/react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "storybook": "^10.4.6" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-iGNmKzrq9vgl2PDrYAnZKI+yvac3Ym+lJXXuQaqlFRS23zA5MNm4EBX+rAG7WulqchoK6NaZ0KQOs2mAgEpTMg=="],
+ "@storybook/svelte-vite": ["@storybook/svelte-vite@10.4.6", "", { "dependencies": { "@storybook/builder-vite": "10.4.6", "@storybook/svelte": "10.4.6", "magic-string": "^0.30.0", "svelte2tsx": "^0.7.44", "typescript": "^4.9.4 || ^5.0.0" }, "peerDependencies": { "@sveltejs/vite-plugin-svelte": "^2.0.0 || ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0", "storybook": "^10.4.6", "svelte": "^5.0.0", "vite": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-zyFg12tksWb0gLK29ucyZO5oNh4veu4biMeJgAEPEALjHw8HysP7mTA4547JYprk3JzORpwSqQ7Y9TPPB0ZwFA=="],
- "@storybook/react-vite": ["@storybook/react-vite@10.4.6", "", { "dependencies": { "@joshwooding/vite-plugin-react-docgen-typescript": "^0.7.0", "@rollup/pluginutils": "^5.0.2", "@storybook/builder-vite": "10.4.6", "@storybook/react": "10.4.6", "empathic": "^2.0.0", "magic-string": "^0.30.0", "react-docgen": "^8.0.0", "resolve": "^1.22.8", "tsconfig-paths": "^4.2.0" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "storybook": "^10.4.6", "vite": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-0arEQtybqGYXHbXpTot+Wv9YtG+V5Vp43QayXavPKQ20M8mpEzhyCPKd0EhqMGSC1Z1UEt0hm365WUBhI9LfKA=="],
+ "@storybook/sveltekit": ["@storybook/sveltekit@10.4.6", "", { "dependencies": { "@storybook/builder-vite": "10.4.6", "@storybook/svelte": "10.4.6", "@storybook/svelte-vite": "10.4.6" }, "peerDependencies": { "storybook": "^10.4.6", "svelte": "^5.0.0", "vite": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-DAbNFUr3B/XiUUUZak2TcjxCxB7eRCCgt4P0MSm9LdFTyePBdU13jybuArUfHqp6lgwqNgbFIjv1nazPjHq51g=="],
- "@tailwindcss/node": ["@tailwindcss/node@4.3.1", "", { "dependencies": { "@jridgewell/remapping": "^2.3.5", "enhanced-resolve": "5.21.6", "jiti": "^2.7.0", "lightningcss": "1.32.0", "magic-string": "^0.30.21", "source-map-js": "^1.2.1", "tailwindcss": "4.3.1" } }, "sha512-6NDaqRoAMSXD1mr/RXu0HBvNE9a2n5tHPsxu9XHLws8o4Twes5rBM2205SUUiJ9goAtadrN6xTGX0UDEwp/N4A=="],
+ "@sveltejs/acorn-typescript": ["@sveltejs/acorn-typescript@1.0.10", "", { "peerDependencies": { "acorn": "^8.9.0" } }, "sha512-4WfKk68eTih+MiJD4fSbxN7E8kVBmTMPWHUPYjvl2N0rMs53YLTT8/YjKU5Dtnz5LqDjl7LEw4U7lXR2W3J5WA=="],
- "@tailwindcss/oxide": ["@tailwindcss/oxide@4.3.1", "", { "optionalDependencies": { "@tailwindcss/oxide-android-arm64": "4.3.1", "@tailwindcss/oxide-darwin-arm64": "4.3.1", "@tailwindcss/oxide-darwin-x64": "4.3.1", "@tailwindcss/oxide-freebsd-x64": "4.3.1", "@tailwindcss/oxide-linux-arm-gnueabihf": "4.3.1", "@tailwindcss/oxide-linux-arm64-gnu": "4.3.1", "@tailwindcss/oxide-linux-arm64-musl": "4.3.1", "@tailwindcss/oxide-linux-x64-gnu": "4.3.1", "@tailwindcss/oxide-linux-x64-musl": "4.3.1", "@tailwindcss/oxide-wasm32-wasi": "4.3.1", "@tailwindcss/oxide-win32-arm64-msvc": "4.3.1", "@tailwindcss/oxide-win32-x64-msvc": "4.3.1" } }, "sha512-yVPyo8RNkabVr3O2EhHEE0Rewu7YKzc1DhIqfL46LKveFrmu9XbDazNOJY7/GRuvw1h6u3utWnR29H/p5JPlgA=="],
+ "@sveltejs/adapter-node": ["@sveltejs/adapter-node@5.5.4", "", { "dependencies": { "@rollup/plugin-commonjs": "^29.0.0", "@rollup/plugin-json": "^6.1.0", "@rollup/plugin-node-resolve": "^16.0.0", "rollup": "^4.59.0" }, "peerDependencies": { "@sveltejs/kit": "^2.4.0" } }, "sha512-45X92CXW+2J8ZUzPv3eLlKWEzINKiiGeFWTjyER4ZN4sGgNoaoeSkCY/QYNxHpPXy71QPsctwccBo9jJs0ySPQ=="],
- "@tailwindcss/oxide-android-arm64": ["@tailwindcss/oxide-android-arm64@4.3.1", "", { "os": "android", "cpu": "arm64" }, "sha512-SVlyf61g374l5cHyg8x9kf5xmLcOaxvOTsbsqDnSsDJaKOEFZ7GCvi84VAVGpxojYOs1+3K6M0UjXfqPU8vmOQ=="],
+ "@sveltejs/kit": ["@sveltejs/kit@2.65.2", "", { "dependencies": { "@standard-schema/spec": "^1.0.0", "@sveltejs/acorn-typescript": "^1.0.9", "@types/cookie": "^0.6.0", "acorn": "^8.16.0", "cookie": "^0.6.0", "devalue": "^5.8.1", "esm-env": "^1.2.2", "kleur": "^4.1.5", "magic-string": "^0.30.5", "mrmime": "^2.0.0", "set-cookie-parser": "^3.0.0", "sirv": "^3.0.0" }, "peerDependencies": { "@opentelemetry/api": "^1.0.0", "@sveltejs/vite-plugin-svelte": "^3.0.0 || ^4.0.0-next.1 || ^5.0.0 || ^6.0.0-next.0 || ^7.0.0", "svelte": "^4.0.0 || ^5.0.0-next.0", "typescript": "^5.3.3 || ^6.0.0", "vite": "^5.0.3 || ^6.0.0 || ^7.0.0-beta.0 || ^8.0.0" }, "optionalPeers": ["@opentelemetry/api", "typescript"], "bin": { "svelte-kit": "svelte-kit.js" } }, "sha512-ZIkyEmxT1gcq50Opn1ZIIx6vc/yt2zNN0rF5hS6op95gqHtNw8QMKDhjJI+RyjMcbvECRw+FzEeAoBe/MOz9AA=="],
- "@tailwindcss/oxide-darwin-arm64": ["@tailwindcss/oxide-darwin-arm64@4.3.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-hVnWLwv+e/l7c4WKyVtHVrIPvYdqWHjRB3MDIqARynzFtnQg85kmQEFCbV9Ja0VVx4xXTIiDWY60Y7iz/iNoDA=="],
+ "@sveltejs/load-config": ["@sveltejs/load-config@0.1.1", "", {}, "sha512-BXXm+VOH/9X4N7Dd1iZ2MqA1h7M+9i2noI8QYuLDY8QcN2WHYn7D/VK/+IJNfcAmRw7ACNJ538UT9GXIhnBTiA=="],
- "@tailwindcss/oxide-darwin-x64": ["@tailwindcss/oxide-darwin-x64@4.3.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-Cf7abu0WVgbhU7ANgPUnSAvm7nCvMweusHb8FnaHlLfv/Caq4GYaEZg7ZImzzmjx4lIAfuS8q+eLIS7A7IzxIg=="],
-
- "@tailwindcss/oxide-freebsd-x64": ["@tailwindcss/oxide-freebsd-x64@4.3.1", "", { "os": "freebsd", "cpu": "x64" }, "sha512-ZZqzX2Y+GXtXXfqSfpJhDm60OoZfvLHLCgm+J7NVqgHHJjG/m9ugZI77RwTsVd4fnBJuCFP6Ae6kTJb71UdS8g=="],
-
- "@tailwindcss/oxide-linux-arm-gnueabihf": ["@tailwindcss/oxide-linux-arm-gnueabihf@4.3.1", "", { "os": "linux", "cpu": "arm" }, "sha512-/Ah/xik0LaMYfv9DZ0S/t4pBlBNYOcqtRwusjgovHkvT8ixueWCLyJjsaF5kQIckjb4IT8Q6K6p/iPmZMixYgg=="],
-
- "@tailwindcss/oxide-linux-arm64-gnu": ["@tailwindcss/oxide-linux-arm64-gnu@4.3.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-gqdFoVJlw444GvpnheZLHmvTzSxI/cOUUh2KSNejQjTcYkW062SVD+En0rUgD+QV91bz1XGIGtt1HJd48xUGbQ=="],
-
- "@tailwindcss/oxide-linux-arm64-musl": ["@tailwindcss/oxide-linux-arm64-musl@4.3.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-Bwv9KwOvE0VKa86xPFif9b9c3Y1NxOV1P0gLti/IYaWEsQYZXDlxfGEtA8mdDZ7SG3wyNXAWYT5SIn3giL57oA=="],
-
- "@tailwindcss/oxide-linux-x64-gnu": ["@tailwindcss/oxide-linux-x64-gnu@4.3.1", "", { "os": "linux", "cpu": "x64" }, "sha512-Ymi8O8T15HYQdOUWUtTI6ldN0neHP85FC+Qz32xTcZ7iJXtem/x8ITev0o1e9e5rkqj4lONZfTRLvkmin1+tKg=="],
-
- "@tailwindcss/oxide-linux-x64-musl": ["@tailwindcss/oxide-linux-x64-musl@4.3.1", "", { "os": "linux", "cpu": "x64" }, "sha512-M+P/91qJ6uILLw4k2G93GMDRAXj61SMvFQYt39AqvUqYgExXpLL5aepfns7sj4HiAQeolirQF9E0lzRvdf4zPQ=="],
-
- "@tailwindcss/oxide-wasm32-wasi": ["@tailwindcss/oxide-wasm32-wasi@4.3.1", "", { "dependencies": { "@emnapi/core": "^1.10.0", "@emnapi/runtime": "^1.10.0", "@emnapi/wasi-threads": "^1.2.1", "@napi-rs/wasm-runtime": "^1.1.4", "@tybys/wasm-util": "^0.10.2", "tslib": "^2.8.1" }, "cpu": "none" }, "sha512-zsM8uOeqvVGHsAXsJxsT28ttosFahLJKCLOTUBqRAtKnVgGSRitds9T432QiT8b77Yga7JIBkulIRRlJPtYhRA=="],
-
- "@tailwindcss/oxide-win32-arm64-msvc": ["@tailwindcss/oxide-win32-arm64-msvc@4.3.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-aiNvSq9BsVk8V513lDKlrCFAgf8qBMPZTpgEhInL+NwQqs97mYmupVMrPrgBBSL8Pv/0zXu9MrMF9rMun1ZeNg=="],
-
- "@tailwindcss/oxide-win32-x64-msvc": ["@tailwindcss/oxide-win32-x64-msvc@4.3.1", "", { "os": "win32", "cpu": "x64" }, "sha512-xDEyu1rg290472FEGaKHnzyDyh5QH+AlWvsU5hMoMtPpzmKlRI0jaYKCgSHDYtaQWZOYbMaduSyCwFwY4n1HmA=="],
-
- "@tailwindcss/vite": ["@tailwindcss/vite@4.3.1", "", { "dependencies": { "@tailwindcss/node": "4.3.1", "@tailwindcss/oxide": "4.3.1", "tailwindcss": "4.3.1" }, "peerDependencies": { "vite": "^5.2.0 || ^6 || ^7 || ^8" } }, "sha512-hItDHuIIlEV61R+faXu66s1K36aTurO/Qw0e45Vskz57gXl9pWOT6eg3zmcEui6CZXddbN7zd41bwmvag4JGwQ=="],
+ "@sveltejs/vite-plugin-svelte": ["@sveltejs/vite-plugin-svelte@7.1.2", "", { "dependencies": { "deepmerge": "^4.3.1", "magic-string": "^0.30.21", "obug": "^2.1.0", "vitefu": "^1.1.2" }, "peerDependencies": { "svelte": "^5.46.4", "vite": "^8.0.0-beta.7 || ^8.0.0" } }, "sha512-DrUBA2UXRfDmUX/ZTiEopd3X40yavsJF1FX2RygcuIScHL7o5YX1fMvoYnDhjeJQC4weCOklirpNWlcb2NiSeA=="],
"@testing-library/dom": ["@testing-library/dom@10.4.1", "", { "dependencies": { "@babel/code-frame": "^7.10.4", "@babel/runtime": "^7.12.5", "@types/aria-query": "^5.0.1", "aria-query": "5.3.0", "dom-accessibility-api": "^0.5.9", "lz-string": "^1.5.0", "picocolors": "1.1.1", "pretty-format": "^27.0.2" } }, "sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg=="],
@@ -638,59 +334,27 @@
"@testing-library/user-event": ["@testing-library/user-event@14.6.1", "", { "peerDependencies": { "@testing-library/dom": ">=7.21.4" } }, "sha512-vq7fv0rnt+QTXgPxr5Hjc210p6YKq2kmdziLgnsZGgLJ9e6VAShx1pACLuRjd/AS/sr7phAR58OIIpf0LlmQNw=="],
- "@ts-morph/common": ["@ts-morph/common@0.27.0", "", { "dependencies": { "fast-glob": "^3.3.3", "minimatch": "^10.0.1", "path-browserify": "^1.0.1" } }, "sha512-Wf29UqxWDpc+i61k3oIOzcUfQt79PIT9y/MWfAGlrkjg6lBC1hwDECLXPVJAhWjiGbfBCxZd65F/LIZF3+jeJQ=="],
-
- "@turbo/darwin-64": ["@turbo/darwin-64@2.9.18", "", { "os": "darwin", "cpu": "x64" }, "sha512-9f27peFu16ur8c0v9nUFUEyBnbKuuFsUTjHFWfmwGfzySBXbHwzU44QhZon6Mznz0cHsIr3984NQj/bVrnGSRw=="],
-
- "@turbo/darwin-arm64": ["@turbo/darwin-arm64@2.9.18", "", { "os": "darwin", "cpu": "arm64" }, "sha512-9A6TMRq/Ib+QnbhLlgkhOm+624wO4pzSQ/yQviQfWHOlFvaYxdnIAYmu2H6TS6y7kSVL0DvzNe04NbESTOzFVQ=="],
-
- "@turbo/linux-64": ["@turbo/linux-64@2.9.18", "", { "os": "linux", "cpu": "x64" }, "sha512-zCdIDtz69AnbYh913elJRRoF3QY5aa2HNnf+4rAkc7bQ+tWujiDkCNV7stazOUPggaDvhKIf2Z87qHftTeXSkw=="],
-
- "@turbo/linux-arm64": ["@turbo/linux-arm64@2.9.18", "", { "os": "linux", "cpu": "arm64" }, "sha512-Va1kXI04naMgYwqv/5Dfa36dTDx8015U7oaQAjrXa45ua9OoFjSV4OmvkML4EmXvUclQHCiBRbY8bvd0jV7eAg=="],
-
- "@turbo/windows-64": ["@turbo/windows-64@2.9.18", "", { "os": "win32", "cpu": "x64" }, "sha512-m0kDhZANxSNz9ck1ybogFscHabriAsp4eDFNrN/1H5WrgTF7b3VlcPZnhuO3v2+E2KnCbeAc+UUT10BZZHdDKw=="],
-
- "@turbo/windows-arm64": ["@turbo/windows-arm64@2.9.18", "", { "os": "win32", "cpu": "arm64" }, "sha512-nUdR8WqoomUys9iIQmG45TMiizJ+5BV8egSeLLZba/AWblyp3fVBcIH1kSE58OtK4g2YzbMJEth6Ttv9w5rqMA=="],
-
"@tybys/wasm-util": ["@tybys/wasm-util@0.10.2", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg=="],
"@types/aria-query": ["@types/aria-query@5.0.4", "", {}, "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw=="],
- "@types/babel__core": ["@types/babel__core@7.20.5", "", { "dependencies": { "@babel/parser": "^7.20.7", "@babel/types": "^7.20.7", "@types/babel__generator": "*", "@types/babel__template": "*", "@types/babel__traverse": "*" } }, "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA=="],
-
- "@types/babel__generator": ["@types/babel__generator@7.27.0", "", { "dependencies": { "@babel/types": "^7.0.0" } }, "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg=="],
-
- "@types/babel__template": ["@types/babel__template@7.4.4", "", { "dependencies": { "@babel/parser": "^7.1.0", "@babel/types": "^7.0.0" } }, "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A=="],
-
- "@types/babel__traverse": ["@types/babel__traverse@7.28.0", "", { "dependencies": { "@babel/types": "^7.28.2" } }, "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q=="],
-
"@types/bun": ["@types/bun@1.3.14", "", { "dependencies": { "bun-types": "1.3.14" } }, "sha512-h1hFqFVcvAvD9j9K7ZW7vd82aSA+rTdznZa+5bwvCwqSB1jmmfLcbIWhOLx1/+boy/xmjgCs/OMUL8hRJSmnPw=="],
"@types/chai": ["@types/chai@5.2.3", "", { "dependencies": { "@types/deep-eql": "*", "assertion-error": "^2.0.1" } }, "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA=="],
- "@types/deep-eql": ["@types/deep-eql@4.0.2", "", {}, "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw=="],
+ "@types/cookie": ["@types/cookie@0.6.0", "", {}, "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA=="],
- "@types/doctrine": ["@types/doctrine@0.0.9", "", {}, "sha512-eOIHzCUSH7SMfonMG1LsC2f8vxBFtho6NGBznK41R84YzPuvSBzrhEps33IsQiOW9+VL6NQ9DbjQJznk/S4uRA=="],
+ "@types/deep-eql": ["@types/deep-eql@4.0.2", "", {}, "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw=="],
"@types/estree": ["@types/estree@1.0.9", "", {}, "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg=="],
"@types/node": ["@types/node@25.9.3", "", { "dependencies": { "undici-types": ">=7.24.0 <7.24.7" } }, "sha512-603BddQMv3pUcr4U2dhujk83N2tTDVr/34wII2B6bJy6g+8WD6yUb11jszNs0gdi4PesVWl7ABt8nYMVpnLUcg=="],
- "@types/react": ["@types/react@19.2.17", "", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-MXfmqaVPEVgkBT/aY0aGCkRWWtByiYQXo3xdQ8r5RzuFrPiRn8Gar2tQdXSUQ2GKV3bkXckek89V8wQBY2Q/Aw=="],
-
- "@types/react-dom": ["@types/react-dom@19.2.3", "", { "peerDependencies": { "@types/react": "^19.2.0" } }, "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ=="],
-
"@types/resolve": ["@types/resolve@1.20.2", "", {}, "sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q=="],
- "@types/set-cookie-parser": ["@types/set-cookie-parser@2.4.10", "", { "dependencies": { "@types/node": "*" } }, "sha512-GGmQVGpQWUe5qglJozEjZV/5dyxbOOZ0LHe/lqyWssB88Y4svNfst0uqBVscdDeIKl5Jy5+aPSvy7mI9tYRguw=="],
+ "@types/trusted-types": ["@types/trusted-types@2.0.7", "", {}, "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw=="],
- "@types/statuses": ["@types/statuses@2.0.6", "", {}, "sha512-xMAgYwceFhRA2zY+XbEA7mxYbA093wdiW8Vu6gZPGWy9cmOyU9XesH1tNcEWsKFd5Vzrqx5T3D38PWx1FIIXkA=="],
-
- "@types/validate-npm-package-name": ["@types/validate-npm-package-name@4.0.2", "", {}, "sha512-lrpDziQipxCEeK5kWxvljWYhUvOiB2A9izZd9B2AFarYAkqZshb4lPbRs7zKEic6eGtH8V/2qJW+dPp9OtF6bw=="],
-
- "@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@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/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/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=="],
@@ -700,53 +364,31 @@
"@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@4.1.9", "", {}, "sha512-fHpsS6mIi+PiEW+vcRVOMkX1oSaPKne3VOclSFICPcGOmfKgXPU5iAah+wcNcj2xPrCCmfq99IDGf+EojhhvhA=="],
+ "@vitest/spy": ["@vitest/spy@3.2.4", "", { "dependencies": { "tinyspy": "^4.0.3" } }, "sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw=="],
"@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=="],
"@webcontainer/env": ["@webcontainer/env@1.1.1", "", {}, "sha512-6aN99yL695Hi9SuIk1oC88l9o0gmxL1nGWWQ/kNy81HigJ0FoaoTXpytCj6ItzgyCEwA9kF1wixsTuv5cjsgng=="],
- "accepts": ["accepts@2.0.0", "", { "dependencies": { "mime-types": "^3.0.0", "negotiator": "^1.0.0" } }, "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng=="],
-
"acorn": ["acorn@8.17.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-xRQbDb9BnwDafYNn6Vwl839DYVjqXYb1XVGtWAZ1kcDc6iwAL4hg3B1dZlRiuENFeO2H53gFG3in621AdERVAg=="],
- "agent-base": ["agent-base@7.1.4", "", {}, "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ=="],
-
"ajv": ["ajv@8.20.0", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA=="],
"ajv-formats": ["ajv-formats@3.0.1", "", { "dependencies": { "ajv": "^8.0.0" } }, "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ=="],
- "ansi-colors": ["ansi-colors@4.1.3", "", {}, "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw=="],
-
- "ansi-regex": ["ansi-regex@6.2.2", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="],
+ "ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
"ansi-styles": ["ansi-styles@5.2.0", "", {}, "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA=="],
- "argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="],
-
- "aria-hidden": ["aria-hidden@1.2.6", "", { "dependencies": { "tslib": "^2.0.0" } }, "sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA=="],
-
"aria-query": ["aria-query@5.3.1", "", {}, "sha512-Z/ZeOgVl7bcSYZ/u/rh0fOpvEpq//LZmdbkXyc7syVzjPAhfOa9ebsdTSjEBDU4vs5nC98Kfduj1uFo0qyET3g=="],
"assertion-error": ["assertion-error@2.0.1", "", {}, "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA=="],
"ast-types": ["ast-types@0.16.1", "", { "dependencies": { "tslib": "^2.0.1" } }, "sha512-6t10qk83GOG8p0vKmaCr8eiilZwO171AvbROMtvvNiwrTly62t+7XkA8RdIIVbpMhCASAsxgAzdRSwh6nw/5Dg=="],
- "atomically": ["atomically@1.7.0", "", {}, "sha512-Xcz9l0z7y9yQ9rdDaxlmaI4uJHf/T8g9hOEzJcsEqX2SjCj4J20uK7+ldkDHMbpJDK76wF7xEIgxc/vSlsfw5w=="],
-
"axe-core": ["axe-core@4.12.1", "", {}, "sha512-s7iGf5GaVMxEG0ENN9x+xTr7GFZCb1ZP/1uATUpCEK2X78nDB3RwbtFCo9pGAf9ru+VwoQ464DkaLEeRM08wJA=="],
- "balanced-match": ["balanced-match@4.0.4", "", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="],
-
- "baseline-browser-mapping": ["baseline-browser-mapping@2.10.38", "", { "bin": { "baseline-browser-mapping": "dist/cli.cjs" } }, "sha512-31/02mVB4yuQU6adKk5SlY6m+mxDwUq5KZkyYgnLrrKl7TEm1+3PyDtDBz2kOv/wxZz41GHsvV1A/u6RmiyBvw=="],
-
- "body-parser": ["body-parser@2.3.0", "", { "dependencies": { "bytes": "^3.1.2", "content-type": "^2.0.0", "debug": "^4.4.3", "http-errors": "^2.0.1", "iconv-lite": "^0.7.2", "on-finished": "^2.4.1", "qs": "^6.15.2", "raw-body": "^3.0.2", "type-is": "^2.1.0" } }, "sha512-2cGmJupaNgg+QUwVLAucDuWuoMZ6EX9iHDRswZ5lsNYEmwPaRknMPCLZz07yTzVq/83p4o/wzbDZbBrTvGGTIw=="],
-
- "brace-expansion": ["brace-expansion@5.0.6", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g=="],
-
- "braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="],
-
- "browserslist": ["browserslist@4.28.2", "", { "dependencies": { "baseline-browser-mapping": "^2.10.12", "caniuse-lite": "^1.0.30001782", "electron-to-chromium": "^1.5.328", "node-releases": "^2.0.36", "update-browserslist-db": "^1.2.3" }, "bin": { "browserslist": "cli.js" } }, "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg=="],
+ "axobject-query": ["axobject-query@4.1.0", "", {}, "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ=="],
"buffer-from": ["buffer-from@1.1.2", "", {}, "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="],
@@ -754,74 +396,26 @@
"bundle-name": ["bundle-name@4.1.0", "", { "dependencies": { "run-applescript": "^7.0.0" } }, "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q=="],
- "bytes": ["bytes@3.1.2", "", {}, "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg=="],
-
- "call-bind-apply-helpers": ["call-bind-apply-helpers@1.0.2", "", { "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" } }, "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ=="],
-
- "call-bound": ["call-bound@1.0.4", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "get-intrinsic": "^1.3.0" } }, "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg=="],
-
- "callsites": ["callsites@3.1.0", "", {}, "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ=="],
-
- "caniuse-lite": ["caniuse-lite@1.0.30001799", "", {}, "sha512-hG1bReV+OUU+MOqK4t/ZWI0tZOyz3rqS9XuhOUz1cIcbwBKjOyJEJuw9ER5JuNyqxNk8u/JUVbGibBOL1yrjFw=="],
-
- "chai": ["chai@6.2.2", "", {}, "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg=="],
-
- "chalk": ["chalk@5.6.2", "", {}, "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA=="],
+ "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=="],
"check-error": ["check-error@2.1.3", "", {}, "sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA=="],
- "class-variance-authority": ["class-variance-authority@0.7.1", "", { "dependencies": { "clsx": "^2.1.1" } }, "sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg=="],
-
- "cli-cursor": ["cli-cursor@5.0.0", "", { "dependencies": { "restore-cursor": "^5.0.0" } }, "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw=="],
-
- "cli-spinners": ["cli-spinners@2.9.2", "", {}, "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg=="],
-
- "cli-width": ["cli-width@4.1.0", "", {}, "sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ=="],
-
- "cliui": ["cliui@8.0.1", "", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", "wrap-ansi": "^7.0.0" } }, "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ=="],
+ "chokidar": ["chokidar@4.0.3", "", { "dependencies": { "readdirp": "^4.0.1" } }, "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA=="],
"clsx": ["clsx@2.1.1", "", {}, "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA=="],
- "code-block-writer": ["code-block-writer@13.0.3", "", {}, "sha512-Oofo0pq3IKnsFtuHqSF7TqBfr71aeyZDVJ0HpmqB7FBM2qEigL0iPONSCZSO9pE9dZTAxANe5XHG9Uy0YMv8cg=="],
-
- "color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="],
-
- "color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="],
-
- "commander": ["commander@14.0.3", "", {}, "sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw=="],
-
- "conf": ["conf@10.2.0", "", { "dependencies": { "ajv": "^8.6.3", "ajv-formats": "^2.1.1", "atomically": "^1.7.0", "debounce-fn": "^4.0.0", "dot-prop": "^6.0.1", "env-paths": "^2.2.1", "json-schema-typed": "^7.0.3", "onetime": "^5.1.2", "pkg-up": "^3.1.0", "semver": "^7.3.5" } }, "sha512-8fLl9F04EJqjSqH+QjITQfJF8BrOVaYr1jewVgSRAEWePfxT0sku4w2hrGQ60BC/TNLGQ2pgxNlTbWQmMPFvXg=="],
-
- "content-disposition": ["content-disposition@1.1.0", "", {}, "sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g=="],
-
- "content-type": ["content-type@1.0.5", "", {}, "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA=="],
+ "commondir": ["commondir@1.0.1", "", {}, "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg=="],
"convert-source-map": ["convert-source-map@2.0.0", "", {}, "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="],
- "cookie": ["cookie@1.1.1", "", {}, "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ=="],
-
- "cookie-signature": ["cookie-signature@1.2.2", "", {}, "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg=="],
-
- "cors": ["cors@2.8.6", "", { "dependencies": { "object-assign": "^4", "vary": "^1" } }, "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw=="],
-
- "cosmiconfig": ["cosmiconfig@9.0.2", "", { "dependencies": { "env-paths": "^2.2.1", "import-fresh": "^3.3.0", "js-yaml": "^4.1.0", "parse-json": "^5.2.0" }, "peerDependencies": { "typescript": ">=4.9.5" }, "optionalPeers": ["typescript"] }, "sha512-gtTZxTDau1wL7Y7zifc2dd8jHSK/k6BTx/2Xp/BpdlAdnlYWFVt7qhJqgwi7637yRwRQ3qL4ZidbB4I8tA5VOg=="],
-
- "cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="],
+ "cookie": ["cookie@0.6.0", "", {}, "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw=="],
"css.escape": ["css.escape@1.5.1", "", {}, "sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg=="],
- "cssesc": ["cssesc@3.0.0", "", { "bin": { "cssesc": "bin/cssesc" } }, "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg=="],
-
- "csstype": ["csstype@3.2.3", "", {}, "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="],
-
- "data-uri-to-buffer": ["data-uri-to-buffer@4.0.1", "", {}, "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A=="],
-
- "debounce-fn": ["debounce-fn@4.0.0", "", { "dependencies": { "mimic-fn": "^3.0.0" } }, "sha512-8pYCQiL9Xdcg0UPSD3d+0KMlOjp+KGU5EPwYddgzQ7DATsg4fuUDjQtsYLmWjnk2obnNHgV3vE2Y4jejSOJVBQ=="],
-
- "debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" }, "peerDependencies": { "supports-color": "*" }, "optionalPeers": ["supports-color"] }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="],
-
"dedent": ["dedent@1.7.2", "", { "peerDependencies": { "babel-plugin-macros": "^3.1.0" }, "optionalPeers": ["babel-plugin-macros"] }, "sha512-WzMx3mW98SN+zn3hgemf4OzdmyNhhhKz5Ay0pUfQiMQ3e1g+xmTJWp/pKdwKVXhdSkAEGIIzqeuWrL3mV/AXbA=="],
+ "dedent-js": ["dedent-js@1.0.1", "", {}, "sha512-OUepMozQULMLUmhxS95Vudo0jb0UchLimi3+pQ2plj61Fcy8axbP9hbiD4Sz6DPqn6XG3kfmziVfQ1rSys5AJQ=="],
+
"deep-eql": ["deep-eql@5.0.2", "", {}, "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q=="],
"deepmerge": ["deepmerge@4.3.1", "", {}, "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A=="],
@@ -832,238 +426,68 @@
"define-lazy-prop": ["define-lazy-prop@3.0.0", "", {}, "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg=="],
- "depd": ["depd@2.0.0", "", {}, "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw=="],
-
"dequal": ["dequal@2.0.3", "", {}, "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA=="],
"detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="],
- "detect-node-es": ["detect-node-es@1.1.0", "", {}, "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ=="],
-
- "diff": ["diff@8.0.4", "", {}, "sha512-DPi0FmjiSU5EvQV0++GFDOJ9ASQUVFh5kD+OzOnYdi7n3Wpm9hWWGfB/O2blfHcMVTL5WkQXSnRiK9makhrcnw=="],
-
- "doctrine": ["doctrine@3.0.0", "", { "dependencies": { "esutils": "^2.0.2" } }, "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w=="],
+ "devalue": ["devalue@5.8.1", "", {}, "sha512-4CXDYRBGqN+57wVJkuXBYmpAVUSg3L6JAQa/DFqm238G73E1wuyc/JhGQJzN7vUf/CMphYau2zXbfWzDR5aTEw=="],
"dom-accessibility-api": ["dom-accessibility-api@0.6.3", "", {}, "sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w=="],
- "dot-prop": ["dot-prop@6.0.1", "", { "dependencies": { "is-obj": "^2.0.0" } }, "sha512-tE7ztYzXHIeyvc7N+hR3oi7FIbf/NIjVP9hmAt3yMXzrQ072/fpjGLx2GxNxGxUl5V73MEqYzioOMoVhGMJ5cA=="],
-
- "dotenv": ["dotenv@17.4.2", "", {}, "sha512-nI4U3TottKAcAD9LLud4Cb7b2QztQMUEfHbvhTH09bqXTxnSie8WnjPALV/WMCrJZ6UV/qHJ6L03OqO3LcdYZw=="],
-
"drizzle-kit": ["drizzle-kit@0.31.10", "", { "dependencies": { "@drizzle-team/brocli": "^0.10.2", "@esbuild-kit/esm-loader": "^2.5.5", "esbuild": "^0.25.4", "tsx": "^4.21.0" }, "bin": { "drizzle-kit": "bin.cjs" } }, "sha512-7OZcmQUrdGI+DUNNsKBn1aW8qSoKuTH7d0mYgSP8bAzdFzKoovxEFnoGQp2dVs82EOJeYycqRtciopszwUf8bw=="],
"drizzle-orm": ["drizzle-orm@0.45.2", "", { "peerDependencies": { "@aws-sdk/client-rds-data": ">=3", "@cloudflare/workers-types": ">=4", "@electric-sql/pglite": ">=0.2.0", "@libsql/client": ">=0.10.0", "@libsql/client-wasm": ">=0.10.0", "@neondatabase/serverless": ">=0.10.0", "@op-engineering/op-sqlite": ">=2", "@opentelemetry/api": "^1.4.1", "@planetscale/database": ">=1.13", "@prisma/client": "*", "@tidbcloud/serverless": "*", "@types/better-sqlite3": "*", "@types/pg": "*", "@types/sql.js": "*", "@upstash/redis": ">=1.34.7", "@vercel/postgres": ">=0.8.0", "@xata.io/client": "*", "better-sqlite3": ">=7", "bun-types": "*", "expo-sqlite": ">=14.0.0", "gel": ">=2", "knex": "*", "kysely": "*", "mysql2": ">=2", "pg": ">=8", "postgres": ">=3", "prisma": "*", "sql.js": ">=1", "sqlite3": ">=5" }, "optionalPeers": ["@aws-sdk/client-rds-data", "@cloudflare/workers-types", "@electric-sql/pglite", "@libsql/client", "@libsql/client-wasm", "@neondatabase/serverless", "@op-engineering/op-sqlite", "@opentelemetry/api", "@planetscale/database", "@prisma/client", "@tidbcloud/serverless", "@types/better-sqlite3", "@types/pg", "@types/sql.js", "@upstash/redis", "@vercel/postgres", "@xata.io/client", "better-sqlite3", "bun-types", "expo-sqlite", "gel", "knex", "kysely", "mysql2", "pg", "postgres", "prisma", "sql.js", "sqlite3"] }, "sha512-kY0BSaTNYWnoDMVoyY8uxmyHjpJW1geOmBMdSSicKo9CIIWkSxMIj2rkeSR51b8KAPB7m+qysjuHme5nKP+E5Q=="],
- "dunder-proto": ["dunder-proto@1.0.1", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A=="],
-
- "eciesjs": ["eciesjs@0.4.18", "", { "dependencies": { "@ecies/ciphers": "^0.2.5", "@noble/ciphers": "^1.3.0", "@noble/curves": "^1.9.7", "@noble/hashes": "^1.8.0" } }, "sha512-wG99Zcfcys9fZux7Cft8BAX/YrOJLJSZ3jyYPfhZHqN2E+Ffx+QXBDsv3gubEgPtV6dTzJMSQUwk1H98/t/0wQ=="],
-
- "ee-first": ["ee-first@1.1.1", "", {}, "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="],
-
- "electron-to-chromium": ["electron-to-chromium@1.5.376", "", {}, "sha512-cUVA7/RvbFTEuw/i3obUwDTRIXojaxkResf+ibByPFxjc6XK3VNtcQXV0NSbAlJ0FMjcJGgftVVB4Qo184EXvA=="],
-
- "emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="],
-
- "empathic": ["empathic@2.0.1", "", {}, "sha512-YGRs8knHhKHVShLkFET/rWAU8kmHbOV5LwN938RHI0pljAJ1Gf6SzXsSmRaEzcXTtOOmVqJ5+WtQPL5uigY50Q=="],
-
- "encodeurl": ["encodeurl@2.0.0", "", {}, "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg=="],
-
- "enhanced-resolve": ["enhanced-resolve@5.21.6", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.3.3" } }, "sha512-aNnGCvbJ/RIyWo1IuhNdVjnNF+EjH9wpzpNHt+ci/m9He9LJvUN8wrCcXjp9cWsGNAuvSpVFTx/vraAFQ8qGjQ=="],
-
- "enquirer": ["enquirer@2.4.1", "", { "dependencies": { "ansi-colors": "^4.1.1", "strip-ansi": "^6.0.1" } }, "sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ=="],
-
- "env-paths": ["env-paths@2.2.1", "", {}, "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A=="],
-
- "error-ex": ["error-ex@1.3.4", "", { "dependencies": { "is-arrayish": "^0.2.1" } }, "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ=="],
-
- "es-define-property": ["es-define-property@1.0.1", "", {}, "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g=="],
-
"es-errors": ["es-errors@1.3.0", "", {}, "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw=="],
"es-module-lexer": ["es-module-lexer@2.1.0", "", {}, "sha512-n27zTYMjYu1aj4MjCWzSP7G9r75utsaoc8m61weK+W8JMBGGQybd43GstCXZ3WNmSFtGT9wi59qQTW6mhTR5LQ=="],
- "es-object-atoms": ["es-object-atoms@1.1.2", "", { "dependencies": { "es-errors": "^1.3.0" } }, "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw=="],
+ "es-toolkit": ["es-toolkit@1.47.1", "", {}, "sha512-5RAqEwf4P4E17p+W75KLOWw/nOvKZzSQpxM32IpI2KZLaVonjTrZ0Ai5ghMaVI9eKC2p8eoQgcBdkEDgzFk6+Q=="],
- "esbuild": ["esbuild@0.28.1", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.28.1", "@esbuild/android-arm": "0.28.1", "@esbuild/android-arm64": "0.28.1", "@esbuild/android-x64": "0.28.1", "@esbuild/darwin-arm64": "0.28.1", "@esbuild/darwin-x64": "0.28.1", "@esbuild/freebsd-arm64": "0.28.1", "@esbuild/freebsd-x64": "0.28.1", "@esbuild/linux-arm": "0.28.1", "@esbuild/linux-arm64": "0.28.1", "@esbuild/linux-ia32": "0.28.1", "@esbuild/linux-loong64": "0.28.1", "@esbuild/linux-mips64el": "0.28.1", "@esbuild/linux-ppc64": "0.28.1", "@esbuild/linux-riscv64": "0.28.1", "@esbuild/linux-s390x": "0.28.1", "@esbuild/linux-x64": "0.28.1", "@esbuild/netbsd-arm64": "0.28.1", "@esbuild/netbsd-x64": "0.28.1", "@esbuild/openbsd-arm64": "0.28.1", "@esbuild/openbsd-x64": "0.28.1", "@esbuild/openharmony-arm64": "0.28.1", "@esbuild/sunos-x64": "0.28.1", "@esbuild/win32-arm64": "0.28.1", "@esbuild/win32-ia32": "0.28.1", "@esbuild/win32-x64": "0.28.1" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw=="],
+ "esbuild": ["esbuild@0.25.12", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.12", "@esbuild/android-arm": "0.25.12", "@esbuild/android-arm64": "0.25.12", "@esbuild/android-x64": "0.25.12", "@esbuild/darwin-arm64": "0.25.12", "@esbuild/darwin-x64": "0.25.12", "@esbuild/freebsd-arm64": "0.25.12", "@esbuild/freebsd-x64": "0.25.12", "@esbuild/linux-arm": "0.25.12", "@esbuild/linux-arm64": "0.25.12", "@esbuild/linux-ia32": "0.25.12", "@esbuild/linux-loong64": "0.25.12", "@esbuild/linux-mips64el": "0.25.12", "@esbuild/linux-ppc64": "0.25.12", "@esbuild/linux-riscv64": "0.25.12", "@esbuild/linux-s390x": "0.25.12", "@esbuild/linux-x64": "0.25.12", "@esbuild/netbsd-arm64": "0.25.12", "@esbuild/netbsd-x64": "0.25.12", "@esbuild/openbsd-arm64": "0.25.12", "@esbuild/openbsd-x64": "0.25.12", "@esbuild/openharmony-arm64": "0.25.12", "@esbuild/sunos-x64": "0.25.12", "@esbuild/win32-arm64": "0.25.12", "@esbuild/win32-ia32": "0.25.12", "@esbuild/win32-x64": "0.25.12" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg=="],
- "escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="],
-
- "escape-html": ["escape-html@1.0.3", "", {}, "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow=="],
+ "esm-env": ["esm-env@1.2.2", "", {}, "sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA=="],
"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@3.0.3", "", { "dependencies": { "@types/estree": "^1.0.0" } }, "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g=="],
+ "esrap": ["esrap@1.4.9", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.4.15" } }, "sha512-3OMlcd0a03UGuZpPeUC1HxR3nA23l+HEyCiZw3b3FumJIN9KphoGzDJKMXI1S72jVS1dsenDyQC0kJlO1U9E1g=="],
- "esutils": ["esutils@2.0.3", "", {}, "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="],
-
- "etag": ["etag@1.8.1", "", {}, "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg=="],
-
- "eventsource": ["eventsource@3.0.7", "", { "dependencies": { "eventsource-parser": "^3.0.1" } }, "sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA=="],
-
- "eventsource-parser": ["eventsource-parser@3.1.0", "", {}, "sha512-kJezFj9YFAMLeORyi7aCLxLbD5/qWMQnoMVlVPyHIll7lgRJCc3JVln9Vgl9nwQi0YkMnhdGTMNn7CkRRAptMg=="],
-
- "execa": ["execa@9.6.1", "", { "dependencies": { "@sindresorhus/merge-streams": "^4.0.0", "cross-spawn": "^7.0.6", "figures": "^6.1.0", "get-stream": "^9.0.0", "human-signals": "^8.0.1", "is-plain-obj": "^4.1.0", "is-stream": "^4.0.1", "npm-run-path": "^6.0.0", "pretty-ms": "^9.2.0", "signal-exit": "^4.1.0", "strip-final-newline": "^4.0.0", "yoctocolors": "^2.1.1" } }, "sha512-9Be3ZoN4LmYR90tUoVu2te2BsbzHfhJyfEiAVfz7N5/zv+jduIfLrV2xdQXOHbaD6KgpGdO9PRPM1Y4Q9QkPkA=="],
+ "estree-walker": ["estree-walker@2.0.2", "", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="],
"expect-type": ["expect-type@1.3.0", "", {}, "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA=="],
- "express": ["express@5.2.1", "", { "dependencies": { "accepts": "^2.0.0", "body-parser": "^2.2.1", "content-disposition": "^1.0.0", "content-type": "^1.0.5", "cookie": "^0.7.1", "cookie-signature": "^1.2.1", "debug": "^4.4.0", "depd": "^2.0.0", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", "finalhandler": "^2.1.0", "fresh": "^2.0.0", "http-errors": "^2.0.0", "merge-descriptors": "^2.0.0", "mime-types": "^3.0.0", "on-finished": "^2.4.1", "once": "^1.4.0", "parseurl": "^1.3.3", "proxy-addr": "^2.0.7", "qs": "^6.14.0", "range-parser": "^1.2.1", "router": "^2.2.0", "send": "^1.1.0", "serve-static": "^2.2.0", "statuses": "^2.0.1", "type-is": "^2.0.1", "vary": "^1.1.2" } }, "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw=="],
-
- "express-rate-limit": ["express-rate-limit@8.5.2", "", { "dependencies": { "ip-address": "^10.2.0" }, "peerDependencies": { "express": ">= 4.11" } }, "sha512-5Kb34ipNX694DH48vN9irak1Qx30nb0PLYHXfJgw4YEjiC3ZEmZJhwOp+VfiCYwFzvFTdB9QkArYS5kXa2cx2A=="],
-
"fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="],
- "fast-glob": ["fast-glob@3.3.3", "", { "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", "micromatch": "^4.0.8" } }, "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg=="],
-
- "fast-string-truncated-width": ["fast-string-truncated-width@3.0.3", "", {}, "sha512-0jjjIEL6+0jag3l2XWWizO64/aZVtpiGE3t0Zgqxv0DPuxiMjvB3M24fCyhZUO4KomJQPj3LTSUnDP3GpdwC0g=="],
-
- "fast-string-width": ["fast-string-width@3.0.2", "", { "dependencies": { "fast-string-truncated-width": "^3.0.2" } }, "sha512-gX8LrtNEI5hq8DVUfRQMbr5lpaS4nMIWV+7XEbXk2b8kiQIizgnlr12B4dA3ZEx3308ze0O4Q1R+cHts8kyUJg=="],
-
"fast-uri": ["fast-uri@3.1.2", "", {}, "sha512-rVjf7ArG3LTk+FS6Yw81V1DLuZl1bRbNrev6Tmd/9RaroeeRRJhAt7jg/6YFxbvAQXUCavSoZhPPj6oOx+5KjQ=="],
- "fast-wrap-ansi": ["fast-wrap-ansi@0.2.2", "", { "dependencies": { "fast-string-width": "^3.0.2" } }, "sha512-7F2Fl+TjRSenLqlU3UjSH0iyqopqoZIu7eZVpEirP2g1GtWa2G/ecEmBdgz31+Mxr+ELclgg6sokpSFIQiZ02Q=="],
-
- "fastq": ["fastq@1.20.1", "", { "dependencies": { "reusify": "^1.0.4" } }, "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw=="],
-
"fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="],
- "fetch-blob": ["fetch-blob@3.2.0", "", { "dependencies": { "node-domexception": "^1.0.0", "web-streams-polyfill": "^3.0.3" } }, "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ=="],
-
- "figures": ["figures@6.1.0", "", { "dependencies": { "is-unicode-supported": "^2.0.0" } }, "sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg=="],
-
- "fill-range": ["fill-range@7.1.1", "", { "dependencies": { "to-regex-range": "^5.0.1" } }, "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg=="],
-
- "finalhandler": ["finalhandler@2.1.1", "", { "dependencies": { "debug": "^4.4.0", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "on-finished": "^2.4.1", "parseurl": "^1.3.3", "statuses": "^2.0.1" } }, "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA=="],
-
- "find-up": ["find-up@3.0.0", "", { "dependencies": { "locate-path": "^3.0.0" } }, "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg=="],
-
- "formdata-polyfill": ["formdata-polyfill@4.0.10", "", { "dependencies": { "fetch-blob": "^3.1.2" } }, "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g=="],
-
- "forwarded": ["forwarded@0.2.0", "", {}, "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow=="],
-
- "fresh": ["fresh@2.0.0", "", {}, "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A=="],
-
- "fs-extra": ["fs-extra@11.3.5", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-eKpRKAovdpZtR1WopLHxlBWvAgPny3c4gX1G5Jhwmmw4XJj0ifSD5qB5TOo8hmA0wlRKDAOAhEE1yVPgs6Fgcg=="],
-
"fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="],
"function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="],
- "fuzzysort": ["fuzzysort@3.1.0", "", {}, "sha512-sR9BNCjBg6LNgwvxlBd0sBABvQitkLzoVY9MYYROQVX/FvfJ4Mai9LsGhDgd8qYdds0bY77VzYd5iuB+v5rwQQ=="],
-
- "gensync": ["gensync@1.0.0-beta.2", "", {}, "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg=="],
-
- "get-caller-file": ["get-caller-file@2.0.5", "", {}, "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg=="],
-
- "get-east-asian-width": ["get-east-asian-width@1.6.0", "", {}, "sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA=="],
-
- "get-intrinsic": ["get-intrinsic@1.3.0", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" } }, "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ=="],
-
- "get-nonce": ["get-nonce@1.0.1", "", {}, "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q=="],
-
- "get-own-enumerable-keys": ["get-own-enumerable-keys@1.0.0", "", {}, "sha512-PKsK2FSrQCyxcGHsGrLDcK0lx+0Ke+6e8KFFozA9/fIQLhQzPaRvJFdcz7+Axg3jUH/Mq+NI4xa5u/UT2tQskA=="],
-
- "get-proto": ["get-proto@1.0.1", "", { "dependencies": { "dunder-proto": "^1.0.1", "es-object-atoms": "^1.0.0" } }, "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g=="],
-
- "get-stream": ["get-stream@9.0.1", "", { "dependencies": { "@sec-ant/readable-stream": "^0.4.1", "is-stream": "^4.0.1" } }, "sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA=="],
-
"get-tsconfig": ["get-tsconfig@4.14.0", "", { "dependencies": { "resolve-pkg-maps": "^1.0.0" } }, "sha512-yTb+8DXzDREzgvYmh6s9vHsSVCHeC0G3PI5bEXNBHtmshPnO+S5O7qgLEOn0I5QvMy6kpZN8K1NKGyilLb93wA=="],
- "glob": ["glob@13.0.6", "", { "dependencies": { "minimatch": "^10.2.2", "minipass": "^7.1.3", "path-scurry": "^2.0.2" } }, "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw=="],
-
- "glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="],
-
- "gopd": ["gopd@1.2.0", "", {}, "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg=="],
-
- "graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="],
-
- "graphql": ["graphql@16.14.2", "", {}, "sha512-Chq1s4CY7jmh8gO2qvLIJyfCDIN+EHLFW/9iShnp1z8FjBQMoodWP1kDC36VAMXXIvAjj4ARa7ntfAV2BrjsbA=="],
-
- "has-symbols": ["has-symbols@1.1.0", "", {}, "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ=="],
-
"hasown": ["hasown@2.0.4", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A=="],
- "headers-polyfill": ["headers-polyfill@5.0.1", "", { "dependencies": { "@types/set-cookie-parser": "^2.4.10", "set-cookie-parser": "^3.0.1" } }, "sha512-1TJ6Fih/b8h5TIcv+1+Hw0PDQWJTKDKzFZzcKOiW1wJza3XoAQlkCuXLbymPYB8+ZQyw8mHvdw560e8zVFIWyA=="],
-
- "hono": ["hono@4.12.26", "", {}, "sha512-uyZtpnYxM9CmQ7QsQknM4zN8EftNqhON1qYeIKM0Se67CCEe2c44xyGURwB0axX2fBDu1dqHrHAc1hmNT8ITkw=="],
-
- "http-errors": ["http-errors@2.0.1", "", { "dependencies": { "depd": "~2.0.0", "inherits": "~2.0.4", "setprototypeof": "~1.2.0", "statuses": "~2.0.2", "toidentifier": "~1.0.1" } }, "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ=="],
-
- "https-proxy-agent": ["https-proxy-agent@7.0.6", "", { "dependencies": { "agent-base": "^7.1.2", "debug": "4" } }, "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw=="],
-
- "human-signals": ["human-signals@8.0.1", "", {}, "sha512-eKCa6bwnJhvxj14kZk5NCPc6Hb6BdsU9DZcOnmQKSnO1VKrfV0zCvtttPZUsBvjmNDn8rpcJfpwSYnHBjc95MQ=="],
-
- "iconv-lite": ["iconv-lite@0.7.2", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw=="],
-
- "ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="],
-
- "import-fresh": ["import-fresh@3.3.1", "", { "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" } }, "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ=="],
-
"indent-string": ["indent-string@4.0.0", "", {}, "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg=="],
- "inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="],
-
- "ip-address": ["ip-address@10.2.0", "", {}, "sha512-/+S6j4E9AHvW9SWMSEY9Xfy66O5PWvVEJ08O0y5JGyEKQpojb0K0GKpz/v5HJ/G0vi3D2sjGK78119oXZeE0qA=="],
-
- "ipaddr.js": ["ipaddr.js@1.9.1", "", {}, "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g=="],
-
- "is-arrayish": ["is-arrayish@0.2.1", "", {}, "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg=="],
-
"is-core-module": ["is-core-module@2.16.2", "", { "dependencies": { "hasown": "^2.0.3" } }, "sha512-evOr8xfXKxE6qSR0hSXL2r3sd7ALj8+7jQEUvPYcm5sgZFdJ+AYzT6yNmJenvIYQBgIGwfwz08sL8zoL7yq2BA=="],
"is-docker": ["is-docker@3.0.0", "", { "bin": { "is-docker": "cli.js" } }, "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ=="],
- "is-extglob": ["is-extglob@2.1.1", "", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="],
-
- "is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="],
-
- "is-glob": ["is-glob@4.0.3", "", { "dependencies": { "is-extglob": "^2.1.1" } }, "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg=="],
-
- "is-in-ssh": ["is-in-ssh@1.0.0", "", {}, "sha512-jYa6Q9rH90kR1vKB6NM7qqd1mge3Fx4Dhw5TVlK1MUBqhEOuCagrEHMevNuCcbECmXZ0ThXkRm+Ymr51HwEPAw=="],
-
"is-inside-container": ["is-inside-container@1.0.0", "", { "dependencies": { "is-docker": "^3.0.0" }, "bin": { "is-inside-container": "cli.js" } }, "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA=="],
- "is-interactive": ["is-interactive@2.0.0", "", {}, "sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ=="],
+ "is-module": ["is-module@1.0.0", "", {}, "sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g=="],
- "is-node-process": ["is-node-process@1.2.0", "", {}, "sha512-Vg4o6/fqPxIjtxgUH5QLJhwZ7gW5diGCVlXpuUfELC62CuxM1iHcRe51f2W1FDy04Ai4KJkagKjx3XaqyfRKXw=="],
-
- "is-number": ["is-number@7.0.0", "", {}, "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="],
-
- "is-obj": ["is-obj@3.0.0", "", {}, "sha512-IlsXEHOjtKhpN8r/tRFj2nDyTmHvcfNeu/nrRIcXE17ROeatXchkojffa1SpdqW4cr/Fj6QkEf/Gn4zf6KKvEQ=="],
-
- "is-plain-obj": ["is-plain-obj@4.1.0", "", {}, "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg=="],
-
- "is-promise": ["is-promise@4.0.0", "", {}, "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ=="],
-
- "is-regexp": ["is-regexp@3.1.0", "", {}, "sha512-rbku49cWloU5bSMI+zaRaXdQHXnthP6DZ/vLnfdSKyL4zUzuWnomtOEiZZOd+ioQ+avFo/qau3KPTc7Fjy1uPA=="],
-
- "is-stream": ["is-stream@4.0.1", "", {}, "sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A=="],
-
- "is-unicode-supported": ["is-unicode-supported@2.1.0", "", {}, "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ=="],
+ "is-reference": ["is-reference@3.0.3", "", { "dependencies": { "@types/estree": "^1.0.6" } }, "sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw=="],
"is-wsl": ["is-wsl@3.1.1", "", { "dependencies": { "is-inside-container": "^1.0.0" } }, "sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw=="],
- "isexe": ["isexe@3.1.5", "", {}, "sha512-6B3tLtFqtQS4ekarvLVMZ+X+VlvQekbe4taUkf/rhVO3d/h0M2rfARm/pXLcPEsjjMsFgrFgSrhQIxcSVrBz8w=="],
-
- "jiti": ["jiti@2.7.0", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ=="],
-
- "jose": ["jose@6.2.3", "", {}, "sha512-YYVDInQKFJfR/xa3ojUTl8c2KoTwiL1R5Wg9YCydwH0x0B9grbzlg5HC7mMjCtUJjbQ/YnGEZIhI5tCgfTb4Hw=="],
-
"js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="],
- "js-yaml": ["js-yaml@4.2.0", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-ePWsvanv0DWuDRsW8dnt+R4jQ31SCRCQ7hhNcPXZPsoBZiemuZNYGf7adZdqX2D86j6rvKp3RpCxVTSb8WQlOw=="],
-
- "jsesc": ["jsesc@3.1.0", "", { "bin": { "jsesc": "bin/jsesc" } }, "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA=="],
-
- "json-parse-even-better-errors": ["json-parse-even-better-errors@2.3.1", "", {}, "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w=="],
-
"json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="],
- "json-schema-typed": ["json-schema-typed@8.0.2", "", {}, "sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA=="],
-
- "json5": ["json5@2.2.3", "", { "bin": { "json5": "lib/cli.js" } }, "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg=="],
-
- "jsonfile": ["jsonfile@6.2.1", "", { "dependencies": { "universalify": "^2.0.0" }, "optionalDependencies": { "graceful-fs": "^4.1.6" } }, "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q=="],
-
"kleur": ["kleur@4.1.5", "", {}, "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ=="],
"lightningcss": ["lightningcss@1.32.0", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-android-arm64": "1.32.0", "lightningcss-darwin-arm64": "1.32.0", "lightningcss-darwin-x64": "1.32.0", "lightningcss-freebsd-x64": "1.32.0", "lightningcss-linux-arm-gnueabihf": "1.32.0", "lightningcss-linux-arm64-gnu": "1.32.0", "lightningcss-linux-arm64-musl": "1.32.0", "lightningcss-linux-x64-gnu": "1.32.0", "lightningcss-linux-x64-musl": "1.32.0", "lightningcss-win32-arm64-msvc": "1.32.0", "lightningcss-win32-x64-msvc": "1.32.0" } }, "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ=="],
@@ -1090,118 +514,32 @@
"lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.32.0", "", { "os": "win32", "cpu": "x64" }, "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q=="],
- "lines-and-columns": ["lines-and-columns@1.2.4", "", {}, "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg=="],
-
- "locate-path": ["locate-path@3.0.0", "", { "dependencies": { "p-locate": "^3.0.0", "path-exists": "^3.0.0" } }, "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A=="],
-
- "log-symbols": ["log-symbols@6.0.0", "", { "dependencies": { "chalk": "^5.3.0", "is-unicode-supported": "^1.3.0" } }, "sha512-i24m8rpwhmPIS4zscNzK6MSEhk0DUWa/8iYQWxhffV8jkI4Phvs3F+quL5xvS0gdQR0FyTCMMH33Y78dDTzzIw=="],
+ "locate-character": ["locate-character@3.0.0", "", {}, "sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA=="],
"loupe": ["loupe@3.2.1", "", {}, "sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ=="],
- "lru-cache": ["lru-cache@5.1.1", "", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="],
-
- "lucide-react": ["lucide-react@1.21.0", "", { "peerDependencies": { "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-reEZMXq8Qdd5jg5XYkQ5TR1fB/GiQ7ih4vcrthYDtgjSDwh0i6/YLiGjsWsIwgN49gpAnd4J2elSNzncMEEUUQ=="],
-
"lz-string": ["lz-string@1.5.0", "", { "bin": { "lz-string": "bin/bin.js" } }, "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ=="],
"magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="],
- "math-intrinsics": ["math-intrinsics@1.1.0", "", {}, "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="],
-
- "media-typer": ["media-typer@1.1.0", "", {}, "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw=="],
-
- "merge-descriptors": ["merge-descriptors@2.0.0", "", {}, "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g=="],
-
- "merge-stream": ["merge-stream@2.0.0", "", {}, "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w=="],
-
- "merge2": ["merge2@1.4.1", "", {}, "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg=="],
-
- "micromatch": ["micromatch@4.0.8", "", { "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" } }, "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA=="],
-
- "mime-db": ["mime-db@1.54.0", "", {}, "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ=="],
-
- "mime-types": ["mime-types@3.0.2", "", { "dependencies": { "mime-db": "^1.54.0" } }, "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A=="],
-
- "mimic-fn": ["mimic-fn@3.1.0", "", {}, "sha512-Ysbi9uYW9hFyfrThdDEQuykN4Ey6BuwPD2kpI5ES/nFTDn/98yxYNLZJcgUAKPT/mcrLLKaGzJR9YVxJrIdASQ=="],
-
- "mimic-function": ["mimic-function@5.0.1", "", {}, "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA=="],
-
"min-indent": ["min-indent@1.0.1", "", {}, "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg=="],
- "minimatch": ["minimatch@10.2.5", "", { "dependencies": { "brace-expansion": "^5.0.5" } }, "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg=="],
+ "mri": ["mri@1.2.0", "", {}, "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA=="],
- "minimist": ["minimist@1.2.8", "", {}, "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA=="],
-
- "minipass": ["minipass@7.1.3", "", {}, "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A=="],
-
- "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
-
- "msw": ["msw@2.14.6", "", { "dependencies": { "@inquirer/confirm": "^6.0.11", "@mswjs/interceptors": "^0.41.3", "@open-draft/deferred-promise": "^3.0.0", "@types/statuses": "^2.0.6", "cookie": "^1.1.1", "graphql": "^16.13.2", "headers-polyfill": "^5.0.1", "is-node-process": "^1.2.0", "outvariant": "^1.4.3", "path-to-regexp": "^6.3.0", "picocolors": "^1.1.1", "rettime": "^0.11.11", "statuses": "^2.0.2", "strict-event-emitter": "^0.5.1", "tough-cookie": "^6.0.1", "type-fest": "^5.5.0", "until-async": "^3.0.2", "yargs": "^17.7.2" }, "peerDependencies": { "typescript": ">= 4.8.x" }, "optionalPeers": ["typescript"], "bin": { "msw": "cli/index.js" } }, "sha512-ALe+N10S72cyx94cMcy3Zs4HhXCj35sgeAL4c+WTvKi0zWnbd8/h0lcFqv0mb2P+aSgAdD7p9HzvA0DiUPxsyg=="],
-
- "mute-stream": ["mute-stream@3.0.0", "", {}, "sha512-dkEJPVvun4FryqBmZ5KhDo0K9iDXAwn08tMLDinNdRBNPcYEDiWYysLcc6k3mjTMlbP9KyylvRpd4wFtwrT9rw=="],
+ "mrmime": ["mrmime@2.0.1", "", {}, "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ=="],
"nanoid": ["nanoid@3.3.12", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ=="],
- "negotiator": ["negotiator@1.0.0", "", {}, "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg=="],
-
- "node-domexception": ["node-domexception@1.0.0", "", {}, "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ=="],
-
- "node-fetch": ["node-fetch@3.3.2", "", { "dependencies": { "data-uri-to-buffer": "^4.0.0", "fetch-blob": "^3.1.4", "formdata-polyfill": "^4.0.10" } }, "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA=="],
-
- "node-releases": ["node-releases@2.0.48", "", {}, "sha512-1uz8041X6LoI6ZSdZacM9lVY28vuzDlSKitnpbSNK0RfKoIJkX29NBPVEFXhnuSuEOA9Ww0xnPJ+ILWbGAv8DA=="],
-
- "npm-run-path": ["npm-run-path@6.0.0", "", { "dependencies": { "path-key": "^4.0.0", "unicorn-magic": "^0.3.0" } }, "sha512-9qny7Z9DsQU8Ou39ERsPU4OZQlSTP47ShQzuKZ6PRXpYLtIFgl/DEBYEXKlvcEa+9tHVcK8CF81Y2V72qaZhWA=="],
-
- "object-assign": ["object-assign@4.1.1", "", {}, "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="],
-
- "object-inspect": ["object-inspect@1.13.4", "", {}, "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew=="],
-
- "object-treeify": ["object-treeify@1.1.33", "", {}, "sha512-EFVjAYfzWqWsBMRHPMAXLCDIJnpMhdWAqR7xG6M6a2cs6PMFpl/+Z20w9zDW4vkxOFfddegBKq9Rehd0bxWE7A=="],
-
"obug": ["obug@2.1.3", "", {}, "sha512-9miFgM2OFba7hB+pRgvtV84pYTBaoTHohvmIgiRt6dRIzbwEOIaNaP+dIlGs2fNFoB0SeISs0Jz5WFVRid6Xyg=="],
- "on-finished": ["on-finished@2.4.1", "", { "dependencies": { "ee-first": "1.1.1" } }, "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg=="],
-
- "once": ["once@1.4.0", "", { "dependencies": { "wrappy": "1" } }, "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w=="],
-
- "onetime": ["onetime@5.1.2", "", { "dependencies": { "mimic-fn": "^2.1.0" } }, "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg=="],
-
"open": ["open@10.2.0", "", { "dependencies": { "default-browser": "^5.2.1", "define-lazy-prop": "^3.0.0", "is-inside-container": "^1.0.0", "wsl-utils": "^0.1.0" } }, "sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA=="],
- "ora": ["ora@8.2.0", "", { "dependencies": { "chalk": "^5.3.0", "cli-cursor": "^5.0.0", "cli-spinners": "^2.9.2", "is-interactive": "^2.0.0", "is-unicode-supported": "^2.0.0", "log-symbols": "^6.0.0", "stdin-discarder": "^0.2.2", "string-width": "^7.2.0", "strip-ansi": "^7.1.0" } }, "sha512-weP+BZ8MVNnlCm8c0Qdc1WSWq4Qn7I+9CJGm7Qali6g44e/PUzbjNqJX5NJ9ljlNMosfJvg1fKEGILklK9cwnw=="],
-
- "outvariant": ["outvariant@1.4.3", "", {}, "sha512-+Sl2UErvtsoajRDKCE5/dBz4DIvHXQQnAxtQTF04OJxY0+DyZXSo5P5Bb7XYWOh81syohlYL24hbDwxedPUJCA=="],
-
"oxc-parser": ["oxc-parser@0.127.0", "", { "dependencies": { "@oxc-project/types": "^0.127.0" }, "optionalDependencies": { "@oxc-parser/binding-android-arm-eabi": "0.127.0", "@oxc-parser/binding-android-arm64": "0.127.0", "@oxc-parser/binding-darwin-arm64": "0.127.0", "@oxc-parser/binding-darwin-x64": "0.127.0", "@oxc-parser/binding-freebsd-x64": "0.127.0", "@oxc-parser/binding-linux-arm-gnueabihf": "0.127.0", "@oxc-parser/binding-linux-arm-musleabihf": "0.127.0", "@oxc-parser/binding-linux-arm64-gnu": "0.127.0", "@oxc-parser/binding-linux-arm64-musl": "0.127.0", "@oxc-parser/binding-linux-ppc64-gnu": "0.127.0", "@oxc-parser/binding-linux-riscv64-gnu": "0.127.0", "@oxc-parser/binding-linux-riscv64-musl": "0.127.0", "@oxc-parser/binding-linux-s390x-gnu": "0.127.0", "@oxc-parser/binding-linux-x64-gnu": "0.127.0", "@oxc-parser/binding-linux-x64-musl": "0.127.0", "@oxc-parser/binding-openharmony-arm64": "0.127.0", "@oxc-parser/binding-wasm32-wasi": "0.127.0", "@oxc-parser/binding-win32-arm64-msvc": "0.127.0", "@oxc-parser/binding-win32-ia32-msvc": "0.127.0", "@oxc-parser/binding-win32-x64-msvc": "0.127.0" } }, "sha512-bkgD4qHlN7WxLdX8bLXdaU54TtQtAIg/ZBAfm0aje/mo3MRDo3P0hZSgr4U7O3xfX+fQmR5AP04JS/TGcZLcFA=="],
"oxc-resolver": ["oxc-resolver@11.21.3", "", { "optionalDependencies": { "@oxc-resolver/binding-android-arm-eabi": "11.21.3", "@oxc-resolver/binding-android-arm64": "11.21.3", "@oxc-resolver/binding-darwin-arm64": "11.21.3", "@oxc-resolver/binding-darwin-x64": "11.21.3", "@oxc-resolver/binding-freebsd-x64": "11.21.3", "@oxc-resolver/binding-linux-arm-gnueabihf": "11.21.3", "@oxc-resolver/binding-linux-arm-musleabihf": "11.21.3", "@oxc-resolver/binding-linux-arm64-gnu": "11.21.3", "@oxc-resolver/binding-linux-arm64-musl": "11.21.3", "@oxc-resolver/binding-linux-ppc64-gnu": "11.21.3", "@oxc-resolver/binding-linux-riscv64-gnu": "11.21.3", "@oxc-resolver/binding-linux-riscv64-musl": "11.21.3", "@oxc-resolver/binding-linux-s390x-gnu": "11.21.3", "@oxc-resolver/binding-linux-x64-gnu": "11.21.3", "@oxc-resolver/binding-linux-x64-musl": "11.21.3", "@oxc-resolver/binding-openharmony-arm64": "11.21.3", "@oxc-resolver/binding-wasm32-wasi": "11.21.3", "@oxc-resolver/binding-win32-arm64-msvc": "11.21.3", "@oxc-resolver/binding-win32-x64-msvc": "11.21.3" } }, "sha512-2Mx3fKQz7+xgrBONjsxOgCGtMHOn38/HxMzW1I5efwXB5a4lRN0Vp40gYUJFBWJslcrvwoofTrqoTnLbwTd3pA=="],
- "p-limit": ["p-limit@2.3.0", "", { "dependencies": { "p-try": "^2.0.0" } }, "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w=="],
-
- "p-locate": ["p-locate@3.0.0", "", { "dependencies": { "p-limit": "^2.0.0" } }, "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ=="],
-
- "p-try": ["p-try@2.2.0", "", {}, "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ=="],
-
- "parent-module": ["parent-module@1.0.1", "", { "dependencies": { "callsites": "^3.0.0" } }, "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g=="],
-
- "parse-json": ["parse-json@5.2.0", "", { "dependencies": { "@babel/code-frame": "^7.0.0", "error-ex": "^1.3.1", "json-parse-even-better-errors": "^2.3.0", "lines-and-columns": "^1.1.6" } }, "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg=="],
-
- "parse-ms": ["parse-ms@4.0.0", "", {}, "sha512-TXfryirbmq34y8QBwgqCVLi+8oA3oWx2eAnSn62ITyEhEYaWRlVZ2DvMM9eZbMs/RfxPu/PK/aBLyGj4IrqMHw=="],
-
- "parseurl": ["parseurl@1.3.3", "", {}, "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ=="],
-
- "path-browserify": ["path-browserify@1.0.1", "", {}, "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g=="],
-
- "path-exists": ["path-exists@3.0.0", "", {}, "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ=="],
-
- "path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="],
-
"path-parse": ["path-parse@1.0.7", "", {}, "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw=="],
- "path-scurry": ["path-scurry@2.0.2", "", { "dependencies": { "lru-cache": "^11.0.0", "minipass": "^7.1.2" } }, "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg=="],
-
- "path-to-regexp": ["path-to-regexp@6.3.0", "", {}, "sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ=="],
-
"pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="],
"pathval": ["pathval@2.0.1", "", {}, "sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ=="],
@@ -1210,117 +548,47 @@
"picomatch": ["picomatch@4.0.4", "", {}, "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A=="],
- "pkce-challenge": ["pkce-challenge@5.0.1", "", {}, "sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ=="],
-
- "pkg-up": ["pkg-up@3.1.0", "", { "dependencies": { "find-up": "^3.0.0" } }, "sha512-nDywThFk1i4BQK4twPQ6TA4RT8bDY96yeuCVBWL3ePARCiEKDRSrNGbFIgUJpLp+XeIR65v8ra7WuJOFUBtkMA=="],
-
- "playwright": ["playwright@1.61.0", "", { "dependencies": { "playwright-core": "1.61.0" }, "optionalDependencies": { "fsevents": "2.3.2" }, "bin": { "playwright": "cli.js" } }, "sha512-Z+7BeeqQPRRzklHsVFP4KTGIyMxKUmfeRA4WisM6G3/XW6nwGeX6fX9qYaDa+CiUqpOkb2f6X3nar05R3kSuJQ=="],
-
- "playwright-core": ["playwright-core@1.61.0", "", { "bin": { "playwright-core": "cli.js" } }, "sha512-caX7TrY3Ml6egyDX0WUcTHDxodl/b51y5wJOdCEA36QviK/s2g081hvmGs8eaE3DWb6NYZQ6BjO/QkNRPenoPA=="],
-
"postcss": ["postcss@8.5.15", "", { "dependencies": { "nanoid": "^3.3.12", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A=="],
- "postcss-selector-parser": ["postcss-selector-parser@7.1.4", "", { "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" } }, "sha512-HeP7D2wyhkR+XaK6v4W8oRF62Dsz4flyuczALJp61GckGm42u1saSSJ/0auvcBqxs3jMRFEcPK34At/0JBKdOg=="],
-
- "powershell-utils": ["powershell-utils@0.1.0", "", {}, "sha512-dM0jVuXJPsDN6DvRpea484tCUaMiXWjuCn++HGTqUWzGDjv5tZkEZldAJ/UMlqRYGFrD/etByo4/xOuC/snX2A=="],
-
"pretty-format": ["pretty-format@27.5.1", "", { "dependencies": { "ansi-regex": "^5.0.1", "ansi-styles": "^5.0.0", "react-is": "^17.0.1" } }, "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ=="],
- "pretty-ms": ["pretty-ms@9.3.0", "", { "dependencies": { "parse-ms": "^4.0.0" } }, "sha512-gjVS5hOP+M3wMm5nmNOucbIrqudzs9v/57bWRHQWLYklXqoXKrVfYW2W9+glfGsqtPgpiz5WwyEEB+ksXIx3gQ=="],
-
- "prompts": ["prompts@2.4.2", "", { "dependencies": { "kleur": "^3.0.3", "sisteransi": "^1.0.5" } }, "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q=="],
-
- "proxy-addr": ["proxy-addr@2.0.7", "", { "dependencies": { "forwarded": "0.2.0", "ipaddr.js": "1.9.1" } }, "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg=="],
-
- "qs": ["qs@6.15.2", "", { "dependencies": { "side-channel": "^1.1.0" } }, "sha512-Rzq0KEyX/w/tEybncDgdkZrJgVUsUMk3xjh3t5bv3S1HTAtg+uOYt72+ZfwiQwKdysThkTBdL/rTi6HDmX9Ddw=="],
-
- "queue-microtask": ["queue-microtask@1.2.3", "", {}, "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="],
-
- "radix-ui": ["radix-ui@1.6.0", "", { "dependencies": { "@radix-ui/primitive": "1.1.4", "@radix-ui/react-accessible-icon": "1.1.10", "@radix-ui/react-accordion": "1.2.14", "@radix-ui/react-alert-dialog": "1.1.17", "@radix-ui/react-arrow": "1.1.10", "@radix-ui/react-aspect-ratio": "1.1.10", "@radix-ui/react-avatar": "1.2.0", "@radix-ui/react-checkbox": "1.3.5", "@radix-ui/react-collapsible": "1.1.14", "@radix-ui/react-collection": "1.1.10", "@radix-ui/react-compose-refs": "1.1.3", "@radix-ui/react-context": "1.1.4", "@radix-ui/react-context-menu": "2.3.1", "@radix-ui/react-dialog": "1.1.17", "@radix-ui/react-direction": "1.1.2", "@radix-ui/react-dismissable-layer": "1.1.13", "@radix-ui/react-dropdown-menu": "2.1.18", "@radix-ui/react-focus-guards": "1.1.4", "@radix-ui/react-focus-scope": "1.1.10", "@radix-ui/react-form": "0.1.10", "@radix-ui/react-hover-card": "1.1.17", "@radix-ui/react-label": "2.1.10", "@radix-ui/react-menu": "2.1.18", "@radix-ui/react-menubar": "1.1.18", "@radix-ui/react-navigation-menu": "1.2.16", "@radix-ui/react-one-time-password-field": "0.1.10", "@radix-ui/react-password-toggle-field": "0.1.5", "@radix-ui/react-popover": "1.1.17", "@radix-ui/react-popper": "1.3.1", "@radix-ui/react-portal": "1.1.12", "@radix-ui/react-presence": "1.1.6", "@radix-ui/react-primitive": "2.1.6", "@radix-ui/react-progress": "1.1.10", "@radix-ui/react-radio-group": "1.4.1", "@radix-ui/react-roving-focus": "1.1.13", "@radix-ui/react-scroll-area": "1.2.12", "@radix-ui/react-select": "2.3.1", "@radix-ui/react-separator": "1.1.10", "@radix-ui/react-slider": "1.4.1", "@radix-ui/react-slot": "1.3.0", "@radix-ui/react-switch": "1.3.1", "@radix-ui/react-tabs": "1.1.15", "@radix-ui/react-toast": "1.2.17", "@radix-ui/react-toggle": "1.1.12", "@radix-ui/react-toggle-group": "1.1.13", "@radix-ui/react-toolbar": "1.1.13", "@radix-ui/react-tooltip": "1.2.10", "@radix-ui/react-use-callback-ref": "1.1.2", "@radix-ui/react-use-controllable-state": "1.2.3", "@radix-ui/react-use-effect-event": "0.0.3", "@radix-ui/react-use-escape-keydown": "1.1.2", "@radix-ui/react-use-is-hydrated": "0.1.1", "@radix-ui/react-use-layout-effect": "1.1.2", "@radix-ui/react-use-size": "1.1.2", "@radix-ui/react-visually-hidden": "1.2.6" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-EUEC70O03EgxWMP5aoqfBZ6iLC5bczFagGy7zhSYRt8o5DP7IWNiP3ywetse3L9b8843ExB0OGWZvgbYVJuNeg=="],
-
- "range-parser": ["range-parser@1.2.1", "", {}, "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg=="],
-
- "raw-body": ["raw-body@3.0.2", "", { "dependencies": { "bytes": "~3.1.2", "http-errors": "~2.0.1", "iconv-lite": "~0.7.0", "unpipe": "~1.0.0" } }, "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA=="],
-
"react": ["react@19.2.7", "", {}, "sha512-HNe9WslTbXmFK8o8cmwgAeJFSBvt1bPdHCVKtaaV+WlAN36mpT4hcRpwbf3fY56ar2oIXzsBpOAiIRHAdY0OlQ=="],
- "react-docgen": ["react-docgen@8.0.3", "", { "dependencies": { "@babel/core": "^7.28.0", "@babel/traverse": "^7.28.0", "@babel/types": "^7.28.2", "@types/babel__core": "^7.20.5", "@types/babel__traverse": "^7.20.7", "@types/doctrine": "^0.0.9", "@types/resolve": "^1.20.2", "doctrine": "^3.0.0", "resolve": "^1.22.1", "strip-indent": "^4.0.0" } }, "sha512-aEZ9qP+/M+58x2qgfSFEWH1BxLyHe5+qkLNJOZQb5iGS017jpbRnoKhNRrXPeA6RfBrZO5wZrT9DMC1UqE1f1w=="],
-
- "react-docgen-typescript": ["react-docgen-typescript@2.4.0", "", { "peerDependencies": { "typescript": ">= 4.3.x" } }, "sha512-ZtAp5XTO5HRzQctjPU0ybY0RRCQO19X/8fxn3w7y2VVTUbGHDKULPTL4ky3vB05euSgG5NpALhEhDPvQ56wvXg=="],
-
"react-dom": ["react-dom@19.2.7", "", { "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { "react": "^19.2.7" } }, "sha512-t0BRVXvbiE/o20Hfw669rLbMCDWtYZLvmJigy2f0MxsXF+71pxhR3xOkspmsO8h3ZlNzyibAmtCa3l4lYKk6gQ=="],
"react-is": ["react-is@17.0.2", "", {}, "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w=="],
- "react-remove-scroll": ["react-remove-scroll@2.7.2", "", { "dependencies": { "react-remove-scroll-bar": "^2.3.7", "react-style-singleton": "^2.2.3", "tslib": "^2.1.0", "use-callback-ref": "^1.3.3", "use-sidecar": "^1.1.3" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-Iqb9NjCCTt6Hf+vOdNIZGdTiH1QSqr27H/Ek9sv/a97gfueI/5h1s3yRi1nngzMUaOOToin5dI1dXKdXiF+u0Q=="],
-
- "react-remove-scroll-bar": ["react-remove-scroll-bar@2.3.8", "", { "dependencies": { "react-style-singleton": "^2.2.2", "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" }, "optionalPeers": ["@types/react"] }, "sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q=="],
-
- "react-style-singleton": ["react-style-singleton@2.2.3", "", { "dependencies": { "get-nonce": "^1.0.0", "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ=="],
+ "readdirp": ["readdirp@4.1.2", "", {}, "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg=="],
"recast": ["recast@0.23.11", "", { "dependencies": { "ast-types": "^0.16.1", "esprima": "~4.0.0", "source-map": "~0.6.1", "tiny-invariant": "^1.3.3", "tslib": "^2.0.1" } }, "sha512-YTUo+Flmw4ZXiWfQKGcwwc11KnoRAYgzAE2E7mXKCjSviTKShtxBsN6YUUBB2gtaBzKzeKunxhUwNHQuRryhWA=="],
"redent": ["redent@3.0.0", "", { "dependencies": { "indent-string": "^4.0.0", "strip-indent": "^3.0.0" } }, "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg=="],
- "require-directory": ["require-directory@2.1.1", "", {}, "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q=="],
-
"require-from-string": ["require-from-string@2.0.2", "", {}, "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw=="],
"resolve": ["resolve@1.22.12", "", { "dependencies": { "es-errors": "^1.3.0", "is-core-module": "^2.16.1", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" } }, "sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA=="],
- "resolve-from": ["resolve-from@4.0.0", "", {}, "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="],
-
"resolve-pkg-maps": ["resolve-pkg-maps@1.0.0", "", {}, "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw=="],
- "restore-cursor": ["restore-cursor@5.1.0", "", { "dependencies": { "onetime": "^7.0.0", "signal-exit": "^4.1.0" } }, "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA=="],
-
- "rettime": ["rettime@0.11.11", "", {}, "sha512-ILJRqVWBCTlg9r42fFgwVZx1gnFAcQF8mRoMkbgQfIrjEDf9nbBFDFx00oloOa+Q869FUtaYDXZvEfnecQSCoQ=="],
-
- "reusify": ["reusify@1.1.0", "", {}, "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw=="],
-
"rolldown": ["rolldown@1.0.3", "", { "dependencies": { "@oxc-project/types": "=0.133.0", "@rolldown/pluginutils": "^1.0.0" }, "optionalDependencies": { "@rolldown/binding-android-arm64": "1.0.3", "@rolldown/binding-darwin-arm64": "1.0.3", "@rolldown/binding-darwin-x64": "1.0.3", "@rolldown/binding-freebsd-x64": "1.0.3", "@rolldown/binding-linux-arm-gnueabihf": "1.0.3", "@rolldown/binding-linux-arm64-gnu": "1.0.3", "@rolldown/binding-linux-arm64-musl": "1.0.3", "@rolldown/binding-linux-ppc64-gnu": "1.0.3", "@rolldown/binding-linux-s390x-gnu": "1.0.3", "@rolldown/binding-linux-x64-gnu": "1.0.3", "@rolldown/binding-linux-x64-musl": "1.0.3", "@rolldown/binding-openharmony-arm64": "1.0.3", "@rolldown/binding-wasm32-wasi": "1.0.3", "@rolldown/binding-win32-arm64-msvc": "1.0.3", "@rolldown/binding-win32-x64-msvc": "1.0.3" }, "bin": { "rolldown": "./bin/cli.mjs" } }, "sha512-i00lAJ2ks1BYr7rjNjKC7BcqAS7nVfiT3QX1SI5aY+AFHblCmaUf9OE9dbdzDvW6dJxbi2ZCZiy9v3CcwOiX3g=="],
"rollup": ["rollup@4.62.0", "", { "dependencies": { "@types/estree": "1.0.9" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.62.0", "@rollup/rollup-android-arm64": "4.62.0", "@rollup/rollup-darwin-arm64": "4.62.0", "@rollup/rollup-darwin-x64": "4.62.0", "@rollup/rollup-freebsd-arm64": "4.62.0", "@rollup/rollup-freebsd-x64": "4.62.0", "@rollup/rollup-linux-arm-gnueabihf": "4.62.0", "@rollup/rollup-linux-arm-musleabihf": "4.62.0", "@rollup/rollup-linux-arm64-gnu": "4.62.0", "@rollup/rollup-linux-arm64-musl": "4.62.0", "@rollup/rollup-linux-loong64-gnu": "4.62.0", "@rollup/rollup-linux-loong64-musl": "4.62.0", "@rollup/rollup-linux-ppc64-gnu": "4.62.0", "@rollup/rollup-linux-ppc64-musl": "4.62.0", "@rollup/rollup-linux-riscv64-gnu": "4.62.0", "@rollup/rollup-linux-riscv64-musl": "4.62.0", "@rollup/rollup-linux-s390x-gnu": "4.62.0", "@rollup/rollup-linux-x64-gnu": "4.62.0", "@rollup/rollup-linux-x64-musl": "4.62.0", "@rollup/rollup-openbsd-x64": "4.62.0", "@rollup/rollup-openharmony-arm64": "4.62.0", "@rollup/rollup-win32-arm64-msvc": "4.62.0", "@rollup/rollup-win32-ia32-msvc": "4.62.0", "@rollup/rollup-win32-x64-gnu": "4.62.0", "@rollup/rollup-win32-x64-msvc": "4.62.0", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-nc72Wgq62I7rtDV4izT5/aaS0zxy3kttkinf9586ApknY3jZO9NYsmtc24fUckA0X7Q2v+ML4a15pdUlV5V/jA=="],
- "router": ["router@2.2.0", "", { "dependencies": { "debug": "^4.4.0", "depd": "^2.0.0", "is-promise": "^4.0.0", "parseurl": "^1.3.3", "path-to-regexp": "^8.0.0" } }, "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ=="],
-
"run-applescript": ["run-applescript@7.1.0", "", {}, "sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q=="],
- "run-parallel": ["run-parallel@1.2.0", "", { "dependencies": { "queue-microtask": "^1.2.2" } }, "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA=="],
-
- "safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="],
+ "sade": ["sade@1.8.1", "", { "dependencies": { "mri": "^1.1.0" } }, "sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A=="],
"scheduler": ["scheduler@0.27.0", "", {}, "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q=="],
+ "scule": ["scule@1.3.0", "", {}, "sha512-6FtHJEvt+pVMIB9IBY+IcCJ6Z5f1iQnytgyfKMhDKgmzYG+TeH/wx1y3l27rshSbLiSanrR9ffZDrEsmjlQF2g=="],
+
"semver": ["semver@7.8.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-rUCObTnP32Q08R2uuIrt7r9PlEonuTmtuXYcW6s5kjdlj3xbnwe+21yXptAUYcMAABLkYYTtnmzb3w3EDZfueA=="],
- "send": ["send@1.2.1", "", { "dependencies": { "debug": "^4.4.3", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", "fresh": "^2.0.0", "http-errors": "^2.0.1", "mime-types": "^3.0.2", "ms": "^2.1.3", "on-finished": "^2.4.1", "range-parser": "^1.2.1", "statuses": "^2.0.2" } }, "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ=="],
-
- "serve-static": ["serve-static@2.2.1", "", { "dependencies": { "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "parseurl": "^1.3.3", "send": "^1.2.0" } }, "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw=="],
-
"set-cookie-parser": ["set-cookie-parser@3.1.0", "", {}, "sha512-kjnC1DXBHcxaOaOXBHBeRtltsDG2nUiUni+jP92M9gYdW12rsmx92UsfpH7o5tDRs7I1ZZPSQJQGv3UaRfCiuw=="],
- "setprototypeof": ["setprototypeof@1.2.0", "", {}, "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw=="],
-
- "shadcn": ["shadcn@4.11.0", "", { "dependencies": { "@babel/core": "^7.28.0", "@babel/parser": "^7.28.0", "@babel/plugin-transform-typescript": "^7.28.0", "@babel/preset-typescript": "^7.27.1", "@dotenvx/dotenvx": "^1.48.4", "@modelcontextprotocol/sdk": "^1.26.0", "@types/validate-npm-package-name": "^4.0.2", "browserslist": "^4.26.2", "commander": "^14.0.0", "cosmiconfig": "^9.0.0", "dedent": "^1.6.0", "deepmerge": "^4.3.1", "diff": "^8.0.2", "execa": "^9.6.0", "fast-glob": "^3.3.3", "fs-extra": "^11.3.1", "fuzzysort": "^3.1.0", "https-proxy-agent": "^7.0.6", "kleur": "^4.1.5", "node-fetch": "^3.3.2", "open": "^11.0.0", "ora": "^8.2.0", "postcss": "^8.5.6", "postcss-selector-parser": "^7.1.0", "prompts": "^2.4.2", "recast": "^0.23.11", "stringify-object": "^5.0.0", "tailwind-merge": "^3.0.1", "ts-morph": "^26.0.0", "tsconfig-paths": "^4.2.0", "validate-npm-package-name": "^7.0.1", "zod": "^3.24.1", "zod-to-json-schema": "^3.24.6" }, "bin": { "shadcn": "dist/index.js" } }, "sha512-UV0cchFea9hO7poV1CuEP0wvmYjpAqcxCKdy23bndl2Du2ARtDs8A4xdzfhUjDBeOW1nNpJ6lXmsEpsply2SfQ=="],
-
- "shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="],
-
- "shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="],
-
- "side-channel": ["side-channel@1.1.1", "", { "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.4", "side-channel-list": "^1.0.1", "side-channel-map": "^1.0.1", "side-channel-weakmap": "^1.0.2" } }, "sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ=="],
-
- "side-channel-list": ["side-channel-list@1.0.1", "", { "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.4" } }, "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w=="],
-
- "side-channel-map": ["side-channel-map@1.0.1", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.5", "object-inspect": "^1.13.3" } }, "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA=="],
-
- "side-channel-weakmap": ["side-channel-weakmap@1.0.2", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.5", "object-inspect": "^1.13.3", "side-channel-map": "^1.0.1" } }, "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A=="],
-
"siginfo": ["siginfo@2.0.0", "", {}, "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g=="],
- "signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="],
-
- "sisteransi": ["sisteransi@1.0.5", "", {}, "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg=="],
+ "sirv": ["sirv@3.0.2", "", { "dependencies": { "@polka/url": "^1.0.0-next.24", "mrmime": "^2.0.0", "totalist": "^3.0.0" } }, "sha512-2wcC/oGxHis/BoHkkPwldgiPSYcpZK3JU28WoMVv55yHJgcZ8rlXvuG9iZggz+sU1d4bRgIGASwyWqjxu3FM0g=="],
"source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="],
@@ -1330,40 +598,24 @@
"stackback": ["stackback@0.0.2", "", {}, "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw=="],
- "statuses": ["statuses@2.0.2", "", {}, "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw=="],
-
"std-env": ["std-env@4.1.0", "", {}, "sha512-Rq7ybcX2RuC55r9oaPVEW7/xu3tj8u4GeBYHBWCychFtzMIr86A7e3PPEBPT37sHStKX3+TiX/Fr/ACmJLVlLQ=="],
- "stdin-discarder": ["stdin-discarder@0.2.2", "", {}, "sha512-UhDfHmA92YAlNnCfhmq0VeNL5bDbiZGg7sZ2IvPsXubGkiNa9EC+tUTsjBRsYUAz87btI6/1wf4XoVvQ3uRnmQ=="],
-
"storybook": ["storybook@10.4.6", "", { "dependencies": { "@storybook/global": "^5.0.0", "@storybook/icons": "^2.0.2", "@testing-library/jest-dom": "^6.9.1", "@testing-library/user-event": "^14.6.1", "@vitest/expect": "3.2.4", "@vitest/spy": "3.2.4", "@webcontainer/env": "^1.1.1", "esbuild": "^0.18.0 || ^0.19.0 || ^0.20.0 || ^0.21.0 || ^0.22.0 || ^0.23.0 || ^0.24.0 || ^0.25.0 || ^0.26.0 || ^0.27.0 || ^0.28.0", "open": "^10.2.0", "oxc-parser": "^0.127.0", "oxc-resolver": "^11.19.1", "recast": "^0.23.5", "semver": "^7.7.3", "use-sync-external-store": "^1.5.0", "ws": "^8.18.0" }, "peerDependencies": { "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "prettier": "^2 || ^3", "vite-plus": "^0.1.15" }, "optionalPeers": ["@types/react", "prettier", "vite-plus"], "bin": "./dist/bin/dispatcher.js" }, "sha512-6wkA6LxfDSSilloITsrFOJfsnw0mDUP2h8Ls+lRt8oRsudtz2RWFhLv+Toiwg6NW7hUpdTDc2hzR7DztJid6+A=="],
- "strict-event-emitter": ["strict-event-emitter@0.5.1", "", {}, "sha512-vMgjE/GGEPEFnhFub6pa4FmJBRBVOLpIII2hvCZ8Kzb7K0hlHo7mQv6xYrBvCL2LtAIBwFUK8wvuJgTVSQ5MFQ=="],
-
- "string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],
-
- "stringify-object": ["stringify-object@5.0.0", "", { "dependencies": { "get-own-enumerable-keys": "^1.0.0", "is-obj": "^3.0.0", "is-regexp": "^3.1.0" } }, "sha512-zaJYxz2FtcMb4f+g60KsRNFOpVMUyuJgA51Zi5Z1DOTC3S59+OQiVOzE9GZt0x72uBGWKsQIuBKeF9iusmKFsg=="],
-
- "strip-ansi": ["strip-ansi@7.2.0", "", { "dependencies": { "ansi-regex": "^6.2.2" } }, "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w=="],
-
- "strip-bom": ["strip-bom@3.0.0", "", {}, "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA=="],
-
- "strip-final-newline": ["strip-final-newline@4.0.0", "", {}, "sha512-aulFJcD6YK8V1G7iRB5tigAP4TsHBZZrOV8pjV++zdUwmeV8uzbY7yn6h9MswN62adStNZFuCIx4haBnRuMDaw=="],
-
- "strip-indent": ["strip-indent@4.1.1", "", {}, "sha512-SlyRoSkdh1dYP0PzclLE7r0M9sgbFKKMFXpFRUMNuKhQSbC6VQIGzq3E0qsfvGJaUFJPGv6Ws1NZ/haTAjfbMA=="],
+ "strip-indent": ["strip-indent@3.0.0", "", { "dependencies": { "min-indent": "^1.0.0" } }, "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ=="],
"supports-preserve-symlinks-flag": ["supports-preserve-symlinks-flag@1.0.0", "", {}, "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w=="],
- "systeminformation": ["systeminformation@5.31.7", "", { "os": "!aix", "bin": { "systeminformation": "lib/cli.js" } }, "sha512-/8NC53e5nP9nmhn42/ncdOkyJnOoue/Vy+tJOyUGd1Yv66G069wK4rrziwhrqDETgk78CudTQupw5z19S5uoZw=="],
+ "svelte": ["svelte@5.56.3", "", { "dependencies": { "@jridgewell/remapping": "^2.3.4", "@jridgewell/sourcemap-codec": "^1.5.0", "@sveltejs/acorn-typescript": "^1.0.10", "@types/estree": "^1.0.5", "@types/trusted-types": "^2.0.7", "acorn": "^8.12.1", "aria-query": "5.3.1", "axobject-query": "^4.1.0", "clsx": "^2.1.1", "devalue": "^5.8.1", "esm-env": "^1.2.1", "esrap": "^2.2.11", "is-reference": "^3.0.3", "locate-character": "^3.0.0", "magic-string": "^0.30.11", "zimmerframe": "^1.1.2" } }, "sha512-w7JvrM5IFl5cmfbY0TLik9o7mjRUJmRMhOR51tBPu708Gr/MjbGs7VnJnr/B0CaXeI4vtnOh7RKxDr0cwhMdDA=="],
+
+ "svelte-ast-print": ["svelte-ast-print@0.4.2", "", { "dependencies": { "esrap": "1.2.2", "zimmerframe": "1.1.2" }, "peerDependencies": { "svelte": "^5.0.0" } }, "sha512-hRHHufbJoArFmDYQKCpCvc0xUuIEfwYksvyLYEQyH+1xb5LD5sM/IthfooCdXZQtOIqXz6xm7NmaqdfwG4kh6w=="],
+
+ "svelte-check": ["svelte-check@4.6.0", "", { "dependencies": { "@jridgewell/trace-mapping": "^0.3.25", "@sveltejs/load-config": "0.1.1", "chokidar": "^4.0.1", "fdir": "^6.2.0", "picocolors": "^1.0.0", "sade": "^1.7.4" }, "peerDependencies": { "svelte": "^4.0.0 || ^5.0.0-next.0", "typescript": ">=5.0.0" }, "bin": { "svelte-check": "bin/svelte-check" } }, "sha512-KhVnDFDSid57mmZtHz8gfW8AAGylOZ0vPnOIzVmAL+urzwK8sBYXRss953gD8T0OdgAQ11mdWhE6uadmtOz8TQ=="],
+
+ "svelte2tsx": ["svelte2tsx@0.7.56", "", { "dependencies": { "dedent-js": "^1.0.1", "scule": "^1.3.0" }, "peerDependencies": { "svelte": "^3.55 || ^4.0.0-next.0 || ^4.0 || ^5.0.0-next.0", "typescript": "^4.9.4 || ^5.0.0 || ^6.0.0" } }, "sha512-NTvqqL+goYlW8gWNajk81L07+uu7jw5V2m1Az5MZbYm3GEydcHXh+uTrLHM9SuGuaqCtF90vlMXkOVBotfH94g=="],
"tagged-tag": ["tagged-tag@1.0.0", "", {}, "sha512-yEFYrVhod+hdNyx7g5Bnkkb0G6si8HJurOoOEgC8B/O0uXLHlaey/65KRv6cuWBNhBgHKAROVpc7QyYqE5gFng=="],
- "tailwind-merge": ["tailwind-merge@3.6.0", "", {}, "sha512-uxL7qAVQriqRQPAyK3pj66VqskWqoZ37PW94jwOTwNfq/z9oyu1V+eqrZqtR2+fCiXdYOZe/Modt8GtvqNzu+w=="],
-
- "tailwindcss": ["tailwindcss@4.3.1", "", {}, "sha512-hk+TB1m+K8CYNrP6rjQaq/Y+4Zylwpa87mLYBKCunwnnQ9p+fHb7kmSfGqyEJoxF/O6CDyABWVFEafNSYKll+Q=="],
-
- "tapable": ["tapable@2.3.3", "", {}, "sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A=="],
-
"tiny-invariant": ["tiny-invariant@1.3.3", "", {}, "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg=="],
"tinybench": ["tinybench@2.9.0", "", {}, "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg=="],
@@ -1376,120 +628,42 @@
"tinyspy": ["tinyspy@4.0.4", "", {}, "sha512-azl+t0z7pw/z958Gy9svOTuzqIk6xq+NSheJzn5MMWtWTFywIacg2wUlzKFGtt3cthx0r2SxMK0yzJOR0IES7Q=="],
- "tldts": ["tldts@7.4.3", "", { "dependencies": { "tldts-core": "^7.4.3" }, "bin": { "tldts": "bin/cli.js" } }, "sha512-A3BDQBeeukYPzB4QdQ1DtdlUmp4x2OCH8n5UVhEWbyANxNep8GavottKzd1xYKFJKjUgMyPT7EzOfnBO55s8Sg=="],
-
- "tldts-core": ["tldts-core@7.4.3", "", {}, "sha512-27ep5H9PzdBrNd5OFM/j3WCU8F3kPwM9D0BOaOf7uYfxMJfyr0K5Tjj69Gri+sZlh2WXd5buIm47NuPF29CDiw=="],
-
- "to-regex-range": ["to-regex-range@5.0.1", "", { "dependencies": { "is-number": "^7.0.0" } }, "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ=="],
-
- "toidentifier": ["toidentifier@1.0.1", "", {}, "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA=="],
-
- "tough-cookie": ["tough-cookie@6.0.1", "", { "dependencies": { "tldts": "^7.0.5" } }, "sha512-LktZQb3IeoUWB9lqR5EWTHgW/VTITCXg4D21M+lvybRVdylLrRMnqaIONLVb5mav8vM19m44HIcGq4qASeu2Qw=="],
+ "totalist": ["totalist@3.0.1", "", {}, "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ=="],
"ts-dedent": ["ts-dedent@2.3.0", "", {}, "sha512-JfJeIHke7y2egdGGgRAvpCwYFUsHlM2gPcrVOxFkznt/4uzQ7HFmvE63iFHVLBJNDuyDOQgijDK/tXH/f6Msjg=="],
- "ts-morph": ["ts-morph@26.0.0", "", { "dependencies": { "@ts-morph/common": "~0.27.0", "code-block-writer": "^13.0.3" } }, "sha512-ztMO++owQnz8c/gIENcM9XfCEzgoGphTv+nKpYNM1bgsdOVC/jRZuEBf6N+mLLDNg68Kl+GgUZfOySaRiG1/Ug=="],
-
- "tsconfig-paths": ["tsconfig-paths@4.2.0", "", { "dependencies": { "json5": "^2.2.2", "minimist": "^1.2.6", "strip-bom": "^3.0.0" } }, "sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg=="],
-
"tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
"tsx": ["tsx@4.22.4", "", { "dependencies": { "esbuild": "~0.28.0" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "bin": { "tsx": "dist/cli.mjs" } }, "sha512-X8EX+XV4QR5xCsrgxaED954zTDfY8KqlDtskKEL0cHhyS/P8b4IFOvGDQpsC9Q1XnLq915wEfwwY/zzskCtmhg=="],
- "turbo": ["turbo@2.9.18", "", { "optionalDependencies": { "@turbo/darwin-64": "2.9.18", "@turbo/darwin-arm64": "2.9.18", "@turbo/linux-64": "2.9.18", "@turbo/linux-arm64": "2.9.18", "@turbo/windows-64": "2.9.18", "@turbo/windows-arm64": "2.9.18" }, "bin": { "turbo": "bin/turbo" } }, "sha512-bwabv6PupzeavybzEoArBAkwq5fnzwf8OFnRtpHwnviFWuwJPFxtyH+aVp36TmIqK3aYYgtTJ3J0m2ysxxSzQg=="],
-
- "tw-animate-css": ["tw-animate-css@1.4.0", "", {}, "sha512-7bziOlRqH0hJx80h/3mbicLW7o8qLsH5+RaLR2t+OHM3D0JlWGODQKQ4cxbK7WlvmUxpcj6Kgu6EKqjrGFe3QQ=="],
-
- "type-fest": ["type-fest@5.7.0", "", { "dependencies": { "tagged-tag": "^1.0.0" } }, "sha512-1URUxUqfHFM1c+zfSPsa3gnkO7Aq21qyH75SIduNYz4SzY964rn1X2vCMQaHSHhktiw+0kPa2iyb6PUpXqB6Vg=="],
-
- "type-is": ["type-is@2.1.0", "", { "dependencies": { "content-type": "^2.0.0", "media-typer": "^1.1.0", "mime-types": "^3.0.0" } }, "sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA=="],
+ "type-fest": ["type-fest@2.19.0", "", {}, "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA=="],
"typescript": ["typescript@6.0.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw=="],
- "undici": ["undici@7.28.0", "", {}, "sha512-cRZYrTDwWznlnRiPjggAGxZXanty6M8RV1ff8Wm4LWXBp7/IG8v5DnOm74DtUBp9OONpK75YlPnIjQqX0dBDtA=="],
-
"undici-types": ["undici-types@7.24.6", "", {}, "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg=="],
- "unicorn-magic": ["unicorn-magic@0.3.0", "", {}, "sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA=="],
-
- "universalify": ["universalify@2.0.1", "", {}, "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw=="],
-
- "unpipe": ["unpipe@1.0.0", "", {}, "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ=="],
-
"unplugin": ["unplugin@2.3.11", "", { "dependencies": { "@jridgewell/remapping": "^2.3.5", "acorn": "^8.15.0", "picomatch": "^4.0.3", "webpack-virtual-modules": "^0.6.2" } }, "sha512-5uKD0nqiYVzlmCRs01Fhs2BdkEgBS3SAVP6ndrBsuK42iC2+JHyxM05Rm9G8+5mkmRtzMZGY8Ct5+mliZxU/Ww=="],
- "until-async": ["until-async@3.0.2", "", {}, "sha512-IiSk4HlzAMqTUseHHe3VhIGyuFmN90zMTpD3Z3y8jeQbzLIq500MVM7Jq2vUAnTKAFPJrqwkzr6PoTcPhGcOiw=="],
-
- "update-browserslist-db": ["update-browserslist-db@1.2.3", "", { "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" }, "peerDependencies": { "browserslist": ">= 4.21.0" }, "bin": { "update-browserslist-db": "cli.js" } }, "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w=="],
-
- "uplot": ["uplot@1.6.32", "", {}, "sha512-KIMVnG68zvu5XXUbC4LQEPnhwOxBuLyW1AHtpm6IKTXImkbLgkMy+jabjLgSLMasNuGGzQm/ep3tOkyTxpiQIw=="],
-
- "use-callback-ref": ["use-callback-ref@1.3.3", "", { "dependencies": { "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg=="],
-
- "use-sidecar": ["use-sidecar@1.1.3", "", { "dependencies": { "detect-node-es": "^1.1.0", "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ=="],
-
"use-sync-external-store": ["use-sync-external-store@1.6.0", "", { "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w=="],
- "util-deprecate": ["util-deprecate@1.0.2", "", {}, "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="],
-
- "validate-npm-package-name": ["validate-npm-package-name@7.0.2", "", {}, "sha512-hVDIBwsRruT73PbK7uP5ebUt+ezEtCmzZz3F59BSr2F6OVFnJ/6h8liuvdLrQ88Xmnk6/+xGGuq+pG9WwTuy3A=="],
-
- "vary": ["vary@1.1.2", "", {}, "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg=="],
-
"vite": ["vite@8.0.16", "", { "dependencies": { "lightningcss": "^1.32.0", "picomatch": "^4.0.4", "postcss": "^8.5.15", "rolldown": "1.0.3", "tinyglobby": "^0.2.17" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "@vitejs/devtools": "^0.1.18", "esbuild": "^0.27.0 || ^0.28.0", "jiti": ">=1.21.0", "less": "^4.0.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "@vitejs/devtools", "esbuild", "jiti", "less", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-h9bXPmJichP5fLmVQo3PyaGSDE2n3aPuomeAlVRm0JLmt4rY6zmPKd59HYI4LNW8oTK7tlTsuC7l/m7awx9Jcw=="],
- "vitest": ["vitest@4.1.9", "", { "dependencies": { "@vitest/expect": "4.1.9", "@vitest/mocker": "4.1.9", "@vitest/pretty-format": "4.1.9", "@vitest/runner": "4.1.9", "@vitest/snapshot": "4.1.9", "@vitest/spy": "4.1.9", "@vitest/utils": "4.1.9", "es-module-lexer": "^2.0.0", "expect-type": "^1.3.0", "magic-string": "^0.30.21", "obug": "^2.1.1", "pathe": "^2.0.3", "picomatch": "^4.0.3", "std-env": "^4.0.0-rc.1", "tinybench": "^2.9.0", "tinyexec": "^1.0.2", "tinyglobby": "^0.2.15", "tinyrainbow": "^3.1.0", "vite": "^6.0.0 || ^7.0.0 || ^8.0.0", "why-is-node-running": "^2.3.0" }, "peerDependencies": { "@edge-runtime/vm": "*", "@opentelemetry/api": "^1.9.0", "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", "@vitest/browser-playwright": "4.1.9", "@vitest/browser-preview": "4.1.9", "@vitest/browser-webdriverio": "4.1.9", "@vitest/coverage-istanbul": "4.1.9", "@vitest/coverage-v8": "4.1.9", "@vitest/ui": "4.1.9", "happy-dom": "*", "jsdom": "*" }, "optionalPeers": ["@edge-runtime/vm", "@opentelemetry/api", "@types/node", "@vitest/browser-playwright", "@vitest/browser-preview", "@vitest/browser-webdriverio", "@vitest/coverage-istanbul", "@vitest/coverage-v8", "@vitest/ui", "happy-dom", "jsdom"], "bin": { "vitest": "./vitest.mjs" } }, "sha512-nE3/LEyc0z87uHYLZebqCUOaJr2hdtuPp7BQ4BosVFnfltxgAvMG08NyrSGlPpOUWvR27c5flSmYFTNr78L9GQ=="],
+ "vitefu": ["vitefu@1.1.3", "", { "peerDependencies": { "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0" }, "optionalPeers": ["vite"] }, "sha512-ub4okH7Z5KLjb6hDyjqrGXqWtWvoYdU3IGm/NorpgHncKoLTCfRIbvlhBm7r0YstIaQRYlp4yEbFqDcKSzXSSg=="],
- "web-streams-polyfill": ["web-streams-polyfill@3.3.3", "", {}, "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw=="],
+ "vitest": ["vitest@4.1.9", "", { "dependencies": { "@vitest/expect": "4.1.9", "@vitest/mocker": "4.1.9", "@vitest/pretty-format": "4.1.9", "@vitest/runner": "4.1.9", "@vitest/snapshot": "4.1.9", "@vitest/spy": "4.1.9", "@vitest/utils": "4.1.9", "es-module-lexer": "^2.0.0", "expect-type": "^1.3.0", "magic-string": "^0.30.21", "obug": "^2.1.1", "pathe": "^2.0.3", "picomatch": "^4.0.3", "std-env": "^4.0.0-rc.1", "tinybench": "^2.9.0", "tinyexec": "^1.0.2", "tinyglobby": "^0.2.15", "tinyrainbow": "^3.1.0", "vite": "^6.0.0 || ^7.0.0 || ^8.0.0", "why-is-node-running": "^2.3.0" }, "peerDependencies": { "@edge-runtime/vm": "*", "@opentelemetry/api": "^1.9.0", "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", "@vitest/browser-playwright": "4.1.9", "@vitest/browser-preview": "4.1.9", "@vitest/browser-webdriverio": "4.1.9", "@vitest/coverage-istanbul": "4.1.9", "@vitest/coverage-v8": "4.1.9", "@vitest/ui": "4.1.9", "happy-dom": "*", "jsdom": "*" }, "optionalPeers": ["@edge-runtime/vm", "@opentelemetry/api", "@types/node", "@vitest/browser-playwright", "@vitest/browser-preview", "@vitest/browser-webdriverio", "@vitest/coverage-istanbul", "@vitest/coverage-v8", "@vitest/ui", "happy-dom", "jsdom"], "bin": { "vitest": "./vitest.mjs" } }, "sha512-nE3/LEyc0z87uHYLZebqCUOaJr2hdtuPp7BQ4BosVFnfltxgAvMG08NyrSGlPpOUWvR27c5flSmYFTNr78L9GQ=="],
"webpack-virtual-modules": ["webpack-virtual-modules@0.6.2", "", {}, "sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ=="],
- "which": ["which@4.0.0", "", { "dependencies": { "isexe": "^3.1.1" }, "bin": { "node-which": "bin/which.js" } }, "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg=="],
-
"why-is-node-running": ["why-is-node-running@2.3.0", "", { "dependencies": { "siginfo": "^2.0.0", "stackback": "0.0.2" }, "bin": { "why-is-node-running": "cli.js" } }, "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w=="],
- "wrap-ansi": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="],
-
- "wrappy": ["wrappy@1.0.2", "", {}, "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="],
-
"ws": ["ws@8.21.0", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g=="],
"wsl-utils": ["wsl-utils@0.1.0", "", { "dependencies": { "is-wsl": "^3.1.0" } }, "sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw=="],
- "y18n": ["y18n@5.0.8", "", {}, "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA=="],
-
- "yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="],
-
- "yargs": ["yargs@17.7.2", "", { "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", "string-width": "^4.2.3", "y18n": "^5.0.5", "yargs-parser": "^21.1.1" } }, "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w=="],
-
- "yargs-parser": ["yargs-parser@21.1.1", "", {}, "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw=="],
-
- "yocto-spinner": ["yocto-spinner@1.2.0", "", { "dependencies": { "yoctocolors": "^2.1.1" } }, "sha512-Yw0hUB6UA3o4YUgKy3oSe9a4cxoaZ9sBfYDw+JSxo6Id0KoJGoxzPA24qqUXYKBWABs/zDSGTz9kww7t3F0XGw=="],
-
- "yoctocolors": ["yoctocolors@2.1.2", "", {}, "sha512-CzhO+pFNo8ajLM2d2IW/R93ipy99LWjtwblvC1RsoSUMZgyLbYFr221TnSNT7GjGdYui6P459mw9JH/g/zW2ug=="],
-
- "zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="],
-
- "zod-to-json-schema": ["zod-to-json-schema@3.25.2", "", { "peerDependencies": { "zod": "^3.25.28 || ^4" } }, "sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA=="],
-
- "@axe-core/playwright/axe-core": ["axe-core@4.11.4", "", {}, "sha512-KunSNx+TVpkAw/6ULfhnx+HWRecjqZGTOyquAoWHYLRSdK1tB5Ihce1ZW+UY3fj33bYAFWPu7W/GRSmmrCGuxA=="],
-
- "@babel/core/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
-
- "@babel/helper-compilation-targets/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
-
- "@babel/helper-create-class-features-plugin/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
-
- "@dotenvx/dotenvx/commander": ["commander@11.1.0", "", {}, "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ=="],
-
- "@dotenvx/dotenvx/execa": ["execa@5.1.1", "", { "dependencies": { "cross-spawn": "^7.0.3", "get-stream": "^6.0.0", "human-signals": "^2.1.0", "is-stream": "^2.0.0", "merge-stream": "^2.0.0", "npm-run-path": "^4.0.1", "onetime": "^5.1.2", "signal-exit": "^3.0.3", "strip-final-newline": "^2.0.0" } }, "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg=="],
-
- "@dotenvx/dotenvx/open": ["open@8.4.2", "", { "dependencies": { "define-lazy-prop": "^2.0.0", "is-docker": "^2.1.1", "is-wsl": "^2.2.0" } }, "sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ=="],
+ "zimmerframe": ["zimmerframe@1.1.4", "", {}, "sha512-B58NGBEoc8Y9MWWCQGl/gq9xBCe4IiKM0a2x7GZdQKOW5Exr8S1W24J6OgM1njK8xCRGvAJIL/MxXHf6SkmQKQ=="],
"@esbuild-kit/core-utils/esbuild": ["esbuild@0.18.20", "", { "optionalDependencies": { "@esbuild/android-arm": "0.18.20", "@esbuild/android-arm64": "0.18.20", "@esbuild/android-x64": "0.18.20", "@esbuild/darwin-arm64": "0.18.20", "@esbuild/darwin-x64": "0.18.20", "@esbuild/freebsd-arm64": "0.18.20", "@esbuild/freebsd-x64": "0.18.20", "@esbuild/linux-arm": "0.18.20", "@esbuild/linux-arm64": "0.18.20", "@esbuild/linux-ia32": "0.18.20", "@esbuild/linux-loong64": "0.18.20", "@esbuild/linux-mips64el": "0.18.20", "@esbuild/linux-ppc64": "0.18.20", "@esbuild/linux-riscv64": "0.18.20", "@esbuild/linux-s390x": "0.18.20", "@esbuild/linux-x64": "0.18.20", "@esbuild/netbsd-x64": "0.18.20", "@esbuild/openbsd-x64": "0.18.20", "@esbuild/sunos-x64": "0.18.20", "@esbuild/win32-arm64": "0.18.20", "@esbuild/win32-ia32": "0.18.20", "@esbuild/win32-x64": "0.18.20" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA=="],
- "@mswjs/interceptors/@open-draft/deferred-promise": ["@open-draft/deferred-promise@2.2.0", "", {}, "sha512-CecwLWx3rhxVQF6V4bAgPS5t+So2sTbPgAzafKkVizyi7tlwpcFpdFqq+wqF2OwNBmqFuu6tOyouTuxgpMfzmA=="],
-
"@oxc-resolver/binding-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.11.0", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.2", "tslib": "^2.4.0" } }, "sha512-l9Oo58x0HOP5znGzVhYW9U3e5wVuA4LAZU2AGezTmkhO1CgQRFDhDg4nneHsu/t3WniXg9QrG2nIXL/ZS8ln8Q=="],
"@oxc-resolver/binding-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.11.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-55coeOFKHv1ywEcUXJtWU5f+Jr/W5tZDvZig8DLKSwUN1JpROQ4rk/SNOQiFWmaR/VKF4zuFyW1B8JduOSv6Pg=="],
@@ -1498,99 +672,39 @@
"@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=="],
+ "@rollup/plugin-commonjs/is-reference": ["is-reference@1.2.1", "", { "dependencies": { "@types/estree": "*" } }, "sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ=="],
- "@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=="],
+ "@storybook/svelte/type-fest": ["type-fest@5.7.0", "", { "dependencies": { "tagged-tag": "^1.0.0" } }, "sha512-1URUxUqfHFM1c+zfSPsa3gnkO7Aq21qyH75SIduNYz4SzY964rn1X2vCMQaHSHhktiw+0kPa2iyb6PUpXqB6Vg=="],
- "@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/wasi-threads": ["@emnapi/wasi-threads@1.2.2", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA=="],
-
- "@tailwindcss/oxide-wasm32-wasi/@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.1.5", "", { "dependencies": { "@tybys/wasm-util": "^0.10.2" }, "peerDependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1" }, "bundled": true }, "sha512-AWPoBRJ9tsnVhor4sjO7rkni+7p+2IAEFj6cx06UgP10jkQHqay/36uRV/bFkgrh18D9vb4cr8Q0Pthskgzy+Q=="],
-
- "@tailwindcss/oxide-wasm32-wasi/@tybys/wasm-util": ["@tybys/wasm-util@0.10.2", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg=="],
-
- "@tailwindcss/oxide-wasm32-wasi/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
+ "@storybook/svelte-vite/typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
"@testing-library/dom/aria-query": ["aria-query@5.3.0", "", { "dependencies": { "dequal": "^2.0.3" } }, "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A=="],
"@testing-library/dom/dom-accessibility-api": ["dom-accessibility-api@0.5.16", "", {}, "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg=="],
- "body-parser/content-type": ["content-type@2.0.0", "", {}, "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ=="],
+ "@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=="],
- "cliui/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
+ "@vitest/expect/tinyrainbow": ["tinyrainbow@2.0.0", "", {}, "sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw=="],
- "conf/ajv-formats": ["ajv-formats@2.1.1", "", { "dependencies": { "ajv": "^8.0.0" } }, "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA=="],
+ "@vitest/mocker/@vitest/spy": ["@vitest/spy@4.1.9", "", {}, "sha512-fHpsS6mIi+PiEW+vcRVOMkX1oSaPKne3VOclSFICPcGOmfKgXPU5iAah+wcNcj2xPrCCmfq99IDGf+EojhhvhA=="],
- "conf/json-schema-typed": ["json-schema-typed@7.0.3", "", {}, "sha512-7DE8mpG+/fVw+dTpjbxnx47TaMnDfOI1jwft9g1VybltZCduyRQPJPvc+zzKY9WPHxhPWczyFuYa6I8Mw4iU5A=="],
-
- "cross-spawn/which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="],
-
- "dot-prop/is-obj": ["is-obj@2.0.0", "", {}, "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w=="],
-
- "drizzle-kit/esbuild": ["esbuild@0.25.12", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.12", "@esbuild/android-arm": "0.25.12", "@esbuild/android-arm64": "0.25.12", "@esbuild/android-x64": "0.25.12", "@esbuild/darwin-arm64": "0.25.12", "@esbuild/darwin-x64": "0.25.12", "@esbuild/freebsd-arm64": "0.25.12", "@esbuild/freebsd-x64": "0.25.12", "@esbuild/linux-arm": "0.25.12", "@esbuild/linux-arm64": "0.25.12", "@esbuild/linux-ia32": "0.25.12", "@esbuild/linux-loong64": "0.25.12", "@esbuild/linux-mips64el": "0.25.12", "@esbuild/linux-ppc64": "0.25.12", "@esbuild/linux-riscv64": "0.25.12", "@esbuild/linux-s390x": "0.25.12", "@esbuild/linux-x64": "0.25.12", "@esbuild/netbsd-arm64": "0.25.12", "@esbuild/netbsd-x64": "0.25.12", "@esbuild/openbsd-arm64": "0.25.12", "@esbuild/openbsd-x64": "0.25.12", "@esbuild/openharmony-arm64": "0.25.12", "@esbuild/sunos-x64": "0.25.12", "@esbuild/win32-arm64": "0.25.12", "@esbuild/win32-ia32": "0.25.12", "@esbuild/win32-x64": "0.25.12" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg=="],
-
- "enquirer/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
-
- "express/cookie": ["cookie@0.7.2", "", {}, "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w=="],
-
- "log-symbols/is-unicode-supported": ["is-unicode-supported@1.3.0", "", {}, "sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ=="],
-
- "micromatch/picomatch": ["picomatch@2.3.2", "", {}, "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA=="],
-
- "npm-run-path/path-key": ["path-key@4.0.0", "", {}, "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ=="],
-
- "onetime/mimic-fn": ["mimic-fn@2.1.0", "", {}, "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg=="],
-
- "ora/string-width": ["string-width@7.2.0", "", { "dependencies": { "emoji-regex": "^10.3.0", "get-east-asian-width": "^1.0.0", "strip-ansi": "^7.1.0" } }, "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ=="],
-
- "path-scurry/lru-cache": ["lru-cache@11.5.1", "", {}, "sha512-RPimw/7aMdv2oqRrxKwvZXcPfwBrn/JZ2xYcY9Hus/6LaS3VOAKVWKWgNLCFSiOm1ESXinjsDlidVU7JlnCN2A=="],
-
- "playwright/fsevents": ["fsevents@2.3.2", "", { "os": "darwin" }, "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA=="],
-
- "pretty-format/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
-
- "prompts/kleur": ["kleur@3.0.3", "", {}, "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w=="],
-
- "redent/strip-indent": ["strip-indent@3.0.0", "", { "dependencies": { "min-indent": "^1.0.0" } }, "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ=="],
-
- "restore-cursor/onetime": ["onetime@7.0.0", "", { "dependencies": { "mimic-function": "^5.0.0" } }, "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ=="],
+ "@vitest/mocker/estree-walker": ["estree-walker@3.0.3", "", { "dependencies": { "@types/estree": "^1.0.0" } }, "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g=="],
"rolldown/@oxc-project/types": ["@oxc-project/types@0.133.0", "", {}, "sha512-KzkdCd6Uxqnf6l3HOw1xfatAlUURA0g14cvBYFyJ5SaNOQbOUvBr9PKArcPcrNIeRsBdgcUzOGrhKveVpvOIGA=="],
- "router/path-to-regexp": ["path-to-regexp@8.4.2", "", {}, "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA=="],
+ "storybook/esbuild": ["esbuild@0.28.1", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.28.1", "@esbuild/android-arm": "0.28.1", "@esbuild/android-arm64": "0.28.1", "@esbuild/android-x64": "0.28.1", "@esbuild/darwin-arm64": "0.28.1", "@esbuild/darwin-x64": "0.28.1", "@esbuild/freebsd-arm64": "0.28.1", "@esbuild/freebsd-x64": "0.28.1", "@esbuild/linux-arm": "0.28.1", "@esbuild/linux-arm64": "0.28.1", "@esbuild/linux-ia32": "0.28.1", "@esbuild/linux-loong64": "0.28.1", "@esbuild/linux-mips64el": "0.28.1", "@esbuild/linux-ppc64": "0.28.1", "@esbuild/linux-riscv64": "0.28.1", "@esbuild/linux-s390x": "0.28.1", "@esbuild/linux-x64": "0.28.1", "@esbuild/netbsd-arm64": "0.28.1", "@esbuild/netbsd-x64": "0.28.1", "@esbuild/openbsd-arm64": "0.28.1", "@esbuild/openbsd-x64": "0.28.1", "@esbuild/openharmony-arm64": "0.28.1", "@esbuild/sunos-x64": "0.28.1", "@esbuild/win32-arm64": "0.28.1", "@esbuild/win32-ia32": "0.28.1", "@esbuild/win32-x64": "0.28.1" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw=="],
- "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=="],
+ "svelte/esrap": ["esrap@2.2.11", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.4.15" }, "peerDependencies": { "@typescript-eslint/types": "^8.2.0" }, "optionalPeers": ["@typescript-eslint/types"] }, "sha512-gPdx+I+BjYEinNMQaBXFjbaJVyoPMU4ZODg5mE+M4DqVG9VusAVHHjcBX+zqyITlI0DIARwDMMzZwAWj36dRoQ=="],
- "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=="],
+ "svelte-ast-print/esrap": ["esrap@1.2.2", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.4.15", "@types/estree": "^1.0.1" } }, "sha512-F2pSJklxx1BlQIQgooczXCPHmcWpn6EsP5oo73LQfonG9fIlIENQ8vMmfGXeojP9MrkzUNAfyU5vdFlR9shHAw=="],
- "storybook/@vitest/spy": ["@vitest/spy@3.2.4", "", { "dependencies": { "tinyspy": "^4.0.3" } }, "sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw=="],
+ "svelte-ast-print/zimmerframe": ["zimmerframe@1.1.2", "", {}, "sha512-rAbqEGa8ovJy4pyBxZM70hg4pE6gDgaQ0Sl9M3enG3I0d6H4XSAM3GeNGLKnsBpuijUow064sf7ww1nutC5/3w=="],
- "string-width/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
+ "tsx/esbuild": ["esbuild@0.28.1", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.28.1", "@esbuild/android-arm": "0.28.1", "@esbuild/android-arm64": "0.28.1", "@esbuild/android-x64": "0.28.1", "@esbuild/darwin-arm64": "0.28.1", "@esbuild/darwin-x64": "0.28.1", "@esbuild/freebsd-arm64": "0.28.1", "@esbuild/freebsd-x64": "0.28.1", "@esbuild/linux-arm": "0.28.1", "@esbuild/linux-arm64": "0.28.1", "@esbuild/linux-ia32": "0.28.1", "@esbuild/linux-loong64": "0.28.1", "@esbuild/linux-mips64el": "0.28.1", "@esbuild/linux-ppc64": "0.28.1", "@esbuild/linux-riscv64": "0.28.1", "@esbuild/linux-s390x": "0.28.1", "@esbuild/linux-x64": "0.28.1", "@esbuild/netbsd-arm64": "0.28.1", "@esbuild/netbsd-x64": "0.28.1", "@esbuild/openbsd-arm64": "0.28.1", "@esbuild/openbsd-x64": "0.28.1", "@esbuild/openharmony-arm64": "0.28.1", "@esbuild/sunos-x64": "0.28.1", "@esbuild/win32-arm64": "0.28.1", "@esbuild/win32-ia32": "0.28.1", "@esbuild/win32-x64": "0.28.1" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw=="],
- "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=="],
- "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=="],
-
- "@dotenvx/dotenvx/execa/get-stream": ["get-stream@6.0.1", "", {}, "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg=="],
-
- "@dotenvx/dotenvx/execa/human-signals": ["human-signals@2.1.0", "", {}, "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw=="],
-
- "@dotenvx/dotenvx/execa/is-stream": ["is-stream@2.0.1", "", {}, "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg=="],
-
- "@dotenvx/dotenvx/execa/npm-run-path": ["npm-run-path@4.0.1", "", { "dependencies": { "path-key": "^3.0.0" } }, "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw=="],
-
- "@dotenvx/dotenvx/execa/signal-exit": ["signal-exit@3.0.7", "", {}, "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="],
-
- "@dotenvx/dotenvx/execa/strip-final-newline": ["strip-final-newline@2.0.0", "", {}, "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA=="],
-
- "@dotenvx/dotenvx/open/define-lazy-prop": ["define-lazy-prop@2.0.0", "", {}, "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og=="],
-
- "@dotenvx/dotenvx/open/is-docker": ["is-docker@2.2.1", "", { "bin": { "is-docker": "cli.js" } }, "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ=="],
-
- "@dotenvx/dotenvx/open/is-wsl": ["is-wsl@2.2.0", "", { "dependencies": { "is-docker": "^2.0.0" } }, "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww=="],
+ "vitest/@vitest/spy": ["@vitest/spy@4.1.9", "", {}, "sha512-fHpsS6mIi+PiEW+vcRVOMkX1oSaPKne3VOclSFICPcGOmfKgXPU5iAah+wcNcj2xPrCCmfq99IDGf+EojhhvhA=="],
"@esbuild-kit/core-utils/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.18.20", "", { "os": "android", "cpu": "arm" }, "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw=="],
@@ -1638,78 +752,112 @@
"@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=="],
- "cliui/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
+ "@vitest/expect/@vitest/utils/@vitest/pretty-format": ["@vitest/pretty-format@3.2.4", "", { "dependencies": { "tinyrainbow": "^2.0.0" } }, "sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA=="],
- "cross-spawn/which/isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="],
+ "storybook/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.28.1", "", { "os": "aix", "cpu": "ppc64" }, "sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ=="],
- "drizzle-kit/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.12", "", { "os": "aix", "cpu": "ppc64" }, "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA=="],
+ "storybook/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.28.1", "", { "os": "android", "cpu": "arm" }, "sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ=="],
- "drizzle-kit/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.25.12", "", { "os": "android", "cpu": "arm" }, "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg=="],
+ "storybook/esbuild/@esbuild/android-arm64": ["@esbuild/android-arm64@0.28.1", "", { "os": "android", "cpu": "arm64" }, "sha512-34EGEbCIAgosYz6goLcopX6Mo7NyGv9tfwEM2/7Ce2VcVRk568iSvniGWcUXIy7wEDR1wzolcxcriFVrWYcwBg=="],
- "drizzle-kit/esbuild/@esbuild/android-arm64": ["@esbuild/android-arm64@0.25.12", "", { "os": "android", "cpu": "arm64" }, "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg=="],
+ "storybook/esbuild/@esbuild/android-x64": ["@esbuild/android-x64@0.28.1", "", { "os": "android", "cpu": "x64" }, "sha512-dbwY7ltSMDWsRatcRpCnES4F+im88OCUgGZjy52shC7GqHRE/cYlxNbB4Z4UpJswpcc4Qxd2oE/ufM0p61IKng=="],
- "drizzle-kit/esbuild/@esbuild/android-x64": ["@esbuild/android-x64@0.25.12", "", { "os": "android", "cpu": "x64" }, "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg=="],
+ "storybook/esbuild/@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.28.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-TZbWkQY7kvTAXbXUT7uVACR5cMHsDiSz9z7ZKAX/RTq/WJEk3QyRr0wZpNhBDX+/0CtdqUIJlOiodQcta6tY3Q=="],
- "drizzle-kit/esbuild/@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.25.12", "", { "os": "darwin", "cpu": "arm64" }, "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg=="],
+ "storybook/esbuild/@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.28.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-zfdzgK9ACBNZLI/CyHTOx81SyNbM6YXn7rxSgX97VjyiPl9W1i4Ka4fgKECEoFCKGpvBj5qArWIGgQjOwkgskQ=="],
- "drizzle-kit/esbuild/@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.25.12", "", { "os": "darwin", "cpu": "x64" }, "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA=="],
+ "storybook/esbuild/@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.28.1", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-wG2EA8ENdEI0qhkSZMjfqrdY+ziCYCPMmtZjjIwOmXFjmyzEHn+UUxk5of+SYsjtfs3VpnlC7QLzSI5hY/rOAw=="],
- "drizzle-kit/esbuild/@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.25.12", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg=="],
+ "storybook/esbuild/@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.28.1", "", { "os": "freebsd", "cpu": "x64" }, "sha512-i7dZ9vQgnvSCzi/rYCXNgtF/U+eKZNJBzu3eTQbRgHnM7tNSizLOkRFAl3qzVc/Op/u5YkHHa4pf/3DOYHthLQ=="],
- "drizzle-kit/esbuild/@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.25.12", "", { "os": "freebsd", "cpu": "x64" }, "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ=="],
+ "storybook/esbuild/@esbuild/linux-arm": ["@esbuild/linux-arm@0.28.1", "", { "os": "linux", "cpu": "arm" }, "sha512-qVXBOHQS+d5Y722GwJzJUtOLlX7km3CraOaGormF1pDtPd2C/l1SHRPgjLunLGe51Sh5YYWKMFDyV4SxgMQYTQ=="],
- "drizzle-kit/esbuild/@esbuild/linux-arm": ["@esbuild/linux-arm@0.25.12", "", { "os": "linux", "cpu": "arm" }, "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw=="],
+ "storybook/esbuild/@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.28.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-yHs+0uc8+nvEAfAfxrWQKK5peSNzBc4PegcMO0EJ2hT71uA7vB8Ihg2e77R2P7SG5uYjPbHlLLmve4LLLRCf0g=="],
- "drizzle-kit/esbuild/@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.25.12", "", { "os": "linux", "cpu": "arm64" }, "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ=="],
+ "storybook/esbuild/@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.28.1", "", { "os": "linux", "cpu": "ia32" }, "sha512-d1z4ZuP0ajrfz/FhGT4vv278rX8KnPPJx8i5+AtK7TYbx9Le9F1hyzurZpkEyjkGa9dUGhQow4C1NmeGvqxN2w=="],
- "drizzle-kit/esbuild/@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.25.12", "", { "os": "linux", "cpu": "ia32" }, "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA=="],
+ "storybook/esbuild/@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.28.1", "", { "os": "linux", "cpu": "none" }, "sha512-M5sRjUVZrkm1OAPR3dlOYzNmN+loZKGVi1VUQGrwuqLcbR6qeAz+famMhjASeH3YVKvZz+zT1jlh/keC3Rj/lg=="],
- "drizzle-kit/esbuild/@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.25.12", "", { "os": "linux", "cpu": "none" }, "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng=="],
+ "storybook/esbuild/@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.28.1", "", { "os": "linux", "cpu": "none" }, "sha512-mRObBZeHh2OxcBFPWE/FjylkRgZdYuiTR3vaTozquCGOH14iP9oN4x4Ge81CoIDYQrXmIxpFumJBu5MtZpnQJQ=="],
- "drizzle-kit/esbuild/@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.25.12", "", { "os": "linux", "cpu": "none" }, "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw=="],
+ "storybook/esbuild/@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.28.1", "", { "os": "linux", "cpu": "ppc64" }, "sha512-slScBsMAb3GFDcdrCgLwZtPYRoH2H/youv10QiZyRjmsP48fznoveWytSgCI/R0ZcUgpc0ZhIUEx6LHts8yrfQ=="],
- "drizzle-kit/esbuild/@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.25.12", "", { "os": "linux", "cpu": "ppc64" }, "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA=="],
+ "storybook/esbuild/@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.28.1", "", { "os": "linux", "cpu": "none" }, "sha512-kw0owk1o0GFETUJyW0jc0G4Yzs0BHZn0JDZ8JRT088vjJYX777BAs1fDGxAC+q831qOs2DTC96mNsG2opdfyyQ=="],
- "drizzle-kit/esbuild/@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.25.12", "", { "os": "linux", "cpu": "none" }, "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w=="],
+ "storybook/esbuild/@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.28.1", "", { "os": "linux", "cpu": "s390x" }, "sha512-/lAIjX8aYFRByhh6L5rYtPEDRqa9de/4V/juOXcta5frjvzXO4/sqEtyytse0g3zZFuWu5cDN0MkLz2qRDD2Ag=="],
- "drizzle-kit/esbuild/@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.25.12", "", { "os": "linux", "cpu": "s390x" }, "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg=="],
+ "storybook/esbuild/@esbuild/linux-x64": ["@esbuild/linux-x64@0.28.1", "", { "os": "linux", "cpu": "x64" }, "sha512-u/anNYF2mmVOEDwLtnQ1wOr3EZ9sTNGLWrsYGYwHWzGA3Si84IOkHXlbWTD1NB+9/1lcnweYKO54uhxZydNzfA=="],
- "drizzle-kit/esbuild/@esbuild/linux-x64": ["@esbuild/linux-x64@0.25.12", "", { "os": "linux", "cpu": "x64" }, "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw=="],
+ "storybook/esbuild/@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.28.1", "", { "os": "none", "cpu": "arm64" }, "sha512-oks0DYbLwWMmaakTsCb+zL4E+aHRVLom9IJZOAthMQEPiQmydXHkziYEsGYRx0uNV/IjEKGAV941JzH02pflqw=="],
- "drizzle-kit/esbuild/@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.25.12", "", { "os": "none", "cpu": "arm64" }, "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg=="],
+ "storybook/esbuild/@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.28.1", "", { "os": "none", "cpu": "x64" }, "sha512-aeL6lAnN89Hz43Mlh1G8ARasbuoYvSITDEx0tHh5b7jJnHcssqgjy9Yx430GDpmCa6OyrKoS0aNRjKundRizGg=="],
- "drizzle-kit/esbuild/@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.25.12", "", { "os": "none", "cpu": "x64" }, "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ=="],
+ "storybook/esbuild/@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.28.1", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-MEFJe5C3R8pwXdZ5Y21oo6m7ePiS0d9pWucn99O/wvyJZChoIQKrQDxKrGeW8F5+T0okTHesAmDeiHDTIq0V/Q=="],
- "drizzle-kit/esbuild/@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.25.12", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A=="],
+ "storybook/esbuild/@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.28.1", "", { "os": "openbsd", "cpu": "x64" }, "sha512-i/ZLIOafE0Z8cI/XANJAixoJL/uRAoS2xOA3rb0xN+KK0K177cMAsQYkzHtBrtMXAKuAc7HGgcWiZ/sRC1Nxgw=="],
- "drizzle-kit/esbuild/@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.25.12", "", { "os": "openbsd", "cpu": "x64" }, "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw=="],
+ "storybook/esbuild/@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.28.1", "", { "os": "none", "cpu": "arm64" }, "sha512-ge+Z7EXFNt2BO1oAMsVpiQ8EwndV9i1xXerAeTIK7AtPs3bKFXQM7nlRxDSIUIMeueR1CNXxqztLzdNeReKBJg=="],
- "drizzle-kit/esbuild/@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.25.12", "", { "os": "none", "cpu": "arm64" }, "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg=="],
+ "storybook/esbuild/@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.28.1", "", { "os": "sunos", "cpu": "x64" }, "sha512-BEjgtECkL3vY+SaSQ6nzVfiALUeFxpawyp8Jmf5PtYhf1Ug40N1h/hxlhts+f1FvSvarEigdxS3BlSMI2PJLcQ=="],
- "drizzle-kit/esbuild/@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.25.12", "", { "os": "sunos", "cpu": "x64" }, "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w=="],
+ "storybook/esbuild/@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.28.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-lCv9eK/H6ZJWbE7bh2nw54CZ9M2nupBxJcTsdk/QQnWkdSjKGuxmmH8/GWrlT1eMmZfn4dGcCjRte397WqfQXA=="],
- "drizzle-kit/esbuild/@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.25.12", "", { "os": "win32", "cpu": "arm64" }, "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg=="],
+ "storybook/esbuild/@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.28.1", "", { "os": "win32", "cpu": "ia32" }, "sha512-zvb/mB2bSCoJOpoCBgYKKpX6YM6mJBlBUVUtVj41DlZJVEB6/0CKlRYxP5wWl1C1ILiCoAU5wZZ4q1P3qeS6Eg=="],
- "drizzle-kit/esbuild/@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.25.12", "", { "os": "win32", "cpu": "ia32" }, "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ=="],
+ "storybook/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.28.1", "", { "os": "win32", "cpu": "x64" }, "sha512-bm4Mowrv+GXMlpWX++EcXw/iLyd1o3+bJkC2DkWXYVvgZCqD/bSj9ctZeAMC3cIxgjRVR2Dufaiu4YPxr5gW1A=="],
- "drizzle-kit/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.12", "", { "os": "win32", "cpu": "x64" }, "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA=="],
+ "tsx/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.28.1", "", { "os": "aix", "cpu": "ppc64" }, "sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ=="],
- "enquirer/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
+ "tsx/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.28.1", "", { "os": "android", "cpu": "arm" }, "sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ=="],
- "ora/string-width/emoji-regex": ["emoji-regex@10.6.0", "", {}, "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A=="],
+ "tsx/esbuild/@esbuild/android-arm64": ["@esbuild/android-arm64@0.28.1", "", { "os": "android", "cpu": "arm64" }, "sha512-34EGEbCIAgosYz6goLcopX6Mo7NyGv9tfwEM2/7Ce2VcVRk568iSvniGWcUXIy7wEDR1wzolcxcriFVrWYcwBg=="],
- "shadcn/open/wsl-utils": ["wsl-utils@0.3.1", "", { "dependencies": { "is-wsl": "^3.1.0", "powershell-utils": "^0.1.0" } }, "sha512-g/eziiSUNBSsdDJtCLB8bdYEUMj4jR7AGeUo96p/3dTafgjHhpF4RiCFPiRILwjQoDXx5MqkBr4fwWtR3Ky4Wg=="],
+ "tsx/esbuild/@esbuild/android-x64": ["@esbuild/android-x64@0.28.1", "", { "os": "android", "cpu": "x64" }, "sha512-dbwY7ltSMDWsRatcRpCnES4F+im88OCUgGZjy52shC7GqHRE/cYlxNbB4Z4UpJswpcc4Qxd2oE/ufM0p61IKng=="],
- "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=="],
+ "tsx/esbuild/@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.28.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-TZbWkQY7kvTAXbXUT7uVACR5cMHsDiSz9z7ZKAX/RTq/WJEk3QyRr0wZpNhBDX+/0CtdqUIJlOiodQcta6tY3Q=="],
- "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=="],
+ "tsx/esbuild/@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.28.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-zfdzgK9ACBNZLI/CyHTOx81SyNbM6YXn7rxSgX97VjyiPl9W1i4Ka4fgKECEoFCKGpvBj5qArWIGgQjOwkgskQ=="],
- "storybook/@vitest/expect/tinyrainbow": ["tinyrainbow@2.0.0", "", {}, "sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw=="],
+ "tsx/esbuild/@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.28.1", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-wG2EA8ENdEI0qhkSZMjfqrdY+ziCYCPMmtZjjIwOmXFjmyzEHn+UUxk5of+SYsjtfs3VpnlC7QLzSI5hY/rOAw=="],
- "string-width/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
+ "tsx/esbuild/@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.28.1", "", { "os": "freebsd", "cpu": "x64" }, "sha512-i7dZ9vQgnvSCzi/rYCXNgtF/U+eKZNJBzu3eTQbRgHnM7tNSizLOkRFAl3qzVc/Op/u5YkHHa4pf/3DOYHthLQ=="],
- "wrap-ansi/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
+ "tsx/esbuild/@esbuild/linux-arm": ["@esbuild/linux-arm@0.28.1", "", { "os": "linux", "cpu": "arm" }, "sha512-qVXBOHQS+d5Y722GwJzJUtOLlX7km3CraOaGormF1pDtPd2C/l1SHRPgjLunLGe51Sh5YYWKMFDyV4SxgMQYTQ=="],
- "storybook/@vitest/expect/@vitest/utils/@vitest/pretty-format": ["@vitest/pretty-format@3.2.4", "", { "dependencies": { "tinyrainbow": "^2.0.0" } }, "sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA=="],
+ "tsx/esbuild/@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.28.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-yHs+0uc8+nvEAfAfxrWQKK5peSNzBc4PegcMO0EJ2hT71uA7vB8Ihg2e77R2P7SG5uYjPbHlLLmve4LLLRCf0g=="],
+
+ "tsx/esbuild/@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.28.1", "", { "os": "linux", "cpu": "ia32" }, "sha512-d1z4ZuP0ajrfz/FhGT4vv278rX8KnPPJx8i5+AtK7TYbx9Le9F1hyzurZpkEyjkGa9dUGhQow4C1NmeGvqxN2w=="],
+
+ "tsx/esbuild/@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.28.1", "", { "os": "linux", "cpu": "none" }, "sha512-M5sRjUVZrkm1OAPR3dlOYzNmN+loZKGVi1VUQGrwuqLcbR6qeAz+famMhjASeH3YVKvZz+zT1jlh/keC3Rj/lg=="],
+
+ "tsx/esbuild/@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.28.1", "", { "os": "linux", "cpu": "none" }, "sha512-mRObBZeHh2OxcBFPWE/FjylkRgZdYuiTR3vaTozquCGOH14iP9oN4x4Ge81CoIDYQrXmIxpFumJBu5MtZpnQJQ=="],
+
+ "tsx/esbuild/@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.28.1", "", { "os": "linux", "cpu": "ppc64" }, "sha512-slScBsMAb3GFDcdrCgLwZtPYRoH2H/youv10QiZyRjmsP48fznoveWytSgCI/R0ZcUgpc0ZhIUEx6LHts8yrfQ=="],
+
+ "tsx/esbuild/@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.28.1", "", { "os": "linux", "cpu": "none" }, "sha512-kw0owk1o0GFETUJyW0jc0G4Yzs0BHZn0JDZ8JRT088vjJYX777BAs1fDGxAC+q831qOs2DTC96mNsG2opdfyyQ=="],
+
+ "tsx/esbuild/@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.28.1", "", { "os": "linux", "cpu": "s390x" }, "sha512-/lAIjX8aYFRByhh6L5rYtPEDRqa9de/4V/juOXcta5frjvzXO4/sqEtyytse0g3zZFuWu5cDN0MkLz2qRDD2Ag=="],
+
+ "tsx/esbuild/@esbuild/linux-x64": ["@esbuild/linux-x64@0.28.1", "", { "os": "linux", "cpu": "x64" }, "sha512-u/anNYF2mmVOEDwLtnQ1wOr3EZ9sTNGLWrsYGYwHWzGA3Si84IOkHXlbWTD1NB+9/1lcnweYKO54uhxZydNzfA=="],
+
+ "tsx/esbuild/@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.28.1", "", { "os": "none", "cpu": "arm64" }, "sha512-oks0DYbLwWMmaakTsCb+zL4E+aHRVLom9IJZOAthMQEPiQmydXHkziYEsGYRx0uNV/IjEKGAV941JzH02pflqw=="],
+
+ "tsx/esbuild/@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.28.1", "", { "os": "none", "cpu": "x64" }, "sha512-aeL6lAnN89Hz43Mlh1G8ARasbuoYvSITDEx0tHh5b7jJnHcssqgjy9Yx430GDpmCa6OyrKoS0aNRjKundRizGg=="],
+
+ "tsx/esbuild/@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.28.1", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-MEFJe5C3R8pwXdZ5Y21oo6m7ePiS0d9pWucn99O/wvyJZChoIQKrQDxKrGeW8F5+T0okTHesAmDeiHDTIq0V/Q=="],
+
+ "tsx/esbuild/@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.28.1", "", { "os": "openbsd", "cpu": "x64" }, "sha512-i/ZLIOafE0Z8cI/XANJAixoJL/uRAoS2xOA3rb0xN+KK0K177cMAsQYkzHtBrtMXAKuAc7HGgcWiZ/sRC1Nxgw=="],
+
+ "tsx/esbuild/@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.28.1", "", { "os": "none", "cpu": "arm64" }, "sha512-ge+Z7EXFNt2BO1oAMsVpiQ8EwndV9i1xXerAeTIK7AtPs3bKFXQM7nlRxDSIUIMeueR1CNXxqztLzdNeReKBJg=="],
+
+ "tsx/esbuild/@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.28.1", "", { "os": "sunos", "cpu": "x64" }, "sha512-BEjgtECkL3vY+SaSQ6nzVfiALUeFxpawyp8Jmf5PtYhf1Ug40N1h/hxlhts+f1FvSvarEigdxS3BlSMI2PJLcQ=="],
+
+ "tsx/esbuild/@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.28.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-lCv9eK/H6ZJWbE7bh2nw54CZ9M2nupBxJcTsdk/QQnWkdSjKGuxmmH8/GWrlT1eMmZfn4dGcCjRte397WqfQXA=="],
+
+ "tsx/esbuild/@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.28.1", "", { "os": "win32", "cpu": "ia32" }, "sha512-zvb/mB2bSCoJOpoCBgYKKpX6YM6mJBlBUVUtVj41DlZJVEB6/0CKlRYxP5wWl1C1ILiCoAU5wZZ4q1P3qeS6Eg=="],
+
+ "tsx/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.28.1", "", { "os": "win32", "cpu": "x64" }, "sha512-bm4Mowrv+GXMlpWX++EcXw/iLyd1o3+bJkC2DkWXYVvgZCqD/bSj9ctZeAMC3cIxgjRVR2Dufaiu4YPxr5gW1A=="],
+
+ "vitest/@vitest/expect/chai": ["chai@6.2.2", "", {}, "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg=="],
}
}
diff --git a/docs/superpowers/plans/2026-06-19-react-runtime-migration.md b/docs/superpowers/plans/2026-06-19-react-runtime-migration.md
deleted file mode 100644
index f79a1d8..0000000
--- a/docs/superpowers/plans/2026-06-19-react-runtime-migration.md
+++ /dev/null
@@ -1,592 +0,0 @@
-# React Runtime Migration Implementation Plan
-
-> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
-
-**Goal:** Replace the SvelteKit dashboard app with a React-based Vite app and Bun production server while preserving model-driven dashboard behavior and reusable presentation boundaries.
-
-**Architecture:** The browser runtime becomes React mounted from `src/main.tsx`. A Bun server at `src/server/index.ts` serves the static React build and exposes JSON API routes for dashboard reads and agent dashboard mutations. Existing model, persistence, datasource, and agent-config modules remain framework-agnostic TypeScript with import path updates as needed.
-
-**Tech Stack:** Bun, Vite, React, React DOM, TypeScript, Tailwind CSS v4, shadcn/ui, Vitest, Storybook React Vite, Playwright, Drizzle ORM, Bun SQLite, MSW, uPlot, Iconify React.
-
----
-
-## File Structure
-
-- Create `index.html`: Vite app shell with `
` and `/src/main.tsx`.
-- Create `src/main.tsx`: React DOM bootstrap and global CSS import.
-- Create `src/App.tsx`: Dashboard fetch, refresh interval, ready/empty/loading/invalid rendering.
-- Create `src/App.test.tsx`: React server-rendering and hook behavior tests for dashboard states.
-- Create `src/server/index.ts`: Bun HTTP server, static asset serving, API router, production entry.
-- Create `src/server/routes/dashboard.ts`: `GET /api/dashboard` runtime loader and datasource resolver.
-- Create `src/server/routes/agent-dashboard.ts`: `POST /api/agent/dashboard` adapter for `handleAgentDashboardRequest`.
-- Create `src/server/routes/dashboard.test.ts`: dashboard API state and datasource-disable tests.
-- Create `src/server/routes/agent-dashboard.test.ts`: agent API delegation/auth tests.
-- Create `src/lib/ui/components/*.tsx`: React ports of current reusable Svelte components.
-- Create `src/lib/ui/components/styles.css`: component CSS migrated from Svelte style blocks.
-- Create `components.json`: shadcn/ui configuration for Vite, Radix, Nova, Tailwind v4, and `$lib` aliases.
-- Create `src/lib/components/ui/*.tsx`: selected shadcn primitives, not the full registry.
-- Create `src/lib/utils.ts`: `cn()` helper for shadcn and dashboard components.
-- Replace `src/lib/ui/components/render.test.ts`: React `renderToString` component tests.
-- Replace `src/lib/ui/stories/*.stories.svelte`: React `.stories.tsx` stories.
-- Modify `.storybook/main.ts`: use `@storybook/react-vite` and React story globs.
-- Modify `.storybook/preview.ts`: use React Storybook types and keep MSW setup/global CSS.
-- Modify `vite.config.ts`: use React and Tailwind plugins, alias `$lib` to `src/lib`, build client, and keep Vitest config.
-- Modify `tsconfig.json`: remove `.svelte-kit` inheritance, enable JSX, and define path aliases.
-- Modify `package.json`: swap Svelte/SvelteKit dependencies for React/Tailwind/shadcn tooling and update scripts.
-- Modify `playwright.config.ts`: build React client and Bun server before e2e.
-- Modify `Containerfile`: copy React/Bun build artifacts and keep `bun build/index.js` command.
-- Modify `README.md`: document React runtime, Bun server, scripts, QA gate, deployment.
-- Remove `svelte.config.js`, `src/app.html`, `src/routes/**`, and all `.svelte` files after replacements pass.
-
-## Task 1: React Toolchain And Typecheck Scaffold
-
-**Files:**
-- Modify: `package.json`
-- Modify: `bun.lock`
-- Modify: `tsconfig.json`
-- Modify: `vite.config.ts`
-- Create: `index.html`
-- Create: `src/main.tsx`
-- Create: `src/App.tsx`
-- Create: `src/App.test.tsx`
-
-- [ ] **Step 1: Write the failing React scaffold test**
-
-Create `src/App.test.tsx`:
-
-```tsx
-import { renderToString } from "react-dom/server";
-import { describe, expect, test } from "vitest";
-import { AppStateView } from "./App";
-
-describe("React app dashboard state view", () => {
- test("renders loading dashboard state", () => {
- const html = renderToString(
- ,
- );
-
- expect(html).toContain("Loading Dashboard");
- expect(html).toContain("Fetching active model");
- });
-});
-```
-
-- [ ] **Step 2: Run the test to verify it fails**
-
-Run: `bun run test:unit src/App.test.tsx`
-
-Expected: FAIL because React dependencies and `src/App.tsx` do not exist.
-
-- [ ] **Step 3: Add React dependencies and scaffold files**
-
-Update `package.json` scripts and dependencies:
-
-```json
-{
- "scripts": {
- "dev": "vite --host 0.0.0.0",
- "build": "vite build && bun build src/server/index.ts --target bun --outdir build",
- "preview": "HOST=0.0.0.0 PORT=4173 bun build/index.js",
- "storybook": "storybook dev -p 6006 --host 0.0.0.0",
- "build-storybook": "storybook build",
- "check": "tsc --noEmit",
- "test": "vitest run",
- "test:unit": "vitest run",
- "test:e2e": "env -u NO_COLOR playwright test",
- "test:qa": "bun run check && bun run test:unit && bun run build && bun run build-storybook && bun run test:e2e",
- "db:generate": "drizzle-kit generate",
- "db:check": "drizzle-kit check"
- }
-}
-```
-
-Install React, Tailwind, and shadcn packages with Bun so `bun.lock` updates:
-
-```sh
-bun add @iconify/react @vitejs/plugin-react react react-dom
-bun add class-variance-authority clsx lucide-react radix-ui tailwind-merge tw-animate-css @fontsource-variable/geist
-bun add -d @storybook/react-vite @tailwindcss/vite @types/react @types/react-dom shadcn tailwindcss
-```
-
-Create `index.html`:
-
-```html
-
-
-
-
-
- Dimension Lab
-
-
-
-
-
-
-```
-
-Create minimal `src/App.tsx`:
-
-```tsx
-import type { DashboardRuntimeState } from "$lib/server/dashboard";
-
-export function AppStateView({ dashboard }: { dashboard: DashboardRuntimeState }) {
- if (dashboard.state === "ready") {
- return {dashboard.document.metadata.title} ;
- }
-
- return (
-
- {dashboard.title}
- {dashboard.subtitle}
- {dashboard.message}
-
- );
-}
-
-export default function App() {
- return (
-
- );
-}
-```
-
-Create `src/main.tsx`:
-
-```tsx
-import { StrictMode } from "react";
-import { createRoot } from "react-dom/client";
-import App from "./App";
-import "./app.css";
-
-const root = document.getElementById("root");
-if (!root) throw new Error("Missing React root element");
-
-createRoot(root).render(
-
-
- ,
-);
-```
-
-Update `tsconfig.json` with React JSX and path aliases.
-
-Update `vite.config.ts` to use `@vitejs/plugin-react`, `@tailwindcss/vite`,
-and the `$lib` alias. Initialize shadcn with:
-
-```sh
-bunx --bun shadcn@latest init --template vite --base radix --preset nova --yes --css-variables
-bunx --bun shadcn@latest add badge card progress separator skeleton alert
-```
-
-Do not run `bunx --bun shadcn@latest add --all`; the dashboard should only
-vendor primitives it actually uses.
-
-- [ ] **Step 4: Run checks for the scaffold**
-
-Run: `bun run test:unit src/App.test.tsx && bun run check`
-
-Expected: PASS.
-
-- [ ] **Step 5: Commit**
-
-```sh
-git add package.json bun.lock tsconfig.json vite.config.ts index.html src/main.tsx src/App.tsx src/App.test.tsx
-git commit -m "build: add react vite scaffold"
-```
-
-## Task 2: Bun Server And Dashboard API
-
-**Files:**
-- Create: `src/server/index.ts`
-- Create: `src/server/routes/dashboard.ts`
-- Create: `src/server/routes/dashboard.test.ts`
-- Create: `src/server/routes/agent-dashboard.ts`
-- Create: `src/server/routes/agent-dashboard.test.ts`
-- Modify: `src/App.tsx`
-- Modify: `playwright.config.ts`
-
-- [ ] **Step 1: Write failing API route tests**
-
-Create `src/server/routes/dashboard.test.ts`:
-
-```ts
-import { describe, expect, test } from "vitest";
-import { loadDashboardResponse } from "./dashboard";
-
-describe("dashboard API route", () => {
- test("returns ready dashboard runtime state from the existing model loader", async () => {
- const response = await loadDashboardResponse({
- disableLiveDatasources: true,
- refreshSeedDocument: true,
- seedIfEmpty: true,
- });
-
- expect(response.state).toBe("ready");
- if (response.state !== "ready") throw new Error("expected ready dashboard");
- expect(response.document.metadata.title).toContain("Dimension Lab");
- });
-});
-```
-
-Create `src/server/routes/agent-dashboard.test.ts`:
-
-```ts
-import { describe, expect, test } from "vitest";
-import { handleAgentDashboardRoute } from "./agent-dashboard";
-
-describe("agent dashboard API route", () => {
- test("delegates unauthorized requests to the existing agent handler", async () => {
- const response = await handleAgentDashboardRoute(
- new Request("http://localhost/api/agent/dashboard", { method: "POST" }),
- );
-
- expect(response.status).toBe(401);
- });
-});
-```
-
-- [ ] **Step 2: Run tests to verify they fail**
-
-Run: `bun run test:unit src/server/routes/dashboard.test.ts src/server/routes/agent-dashboard.test.ts`
-
-Expected: FAIL because the route modules do not exist.
-
-- [ ] **Step 3: Implement server route modules and server entry**
-
-Implement `loadDashboardResponse()` by calling `loadDashboardRuntime()` and
-`resolveDashboardDatasources()` exactly like the current SvelteKit load
-function. Implement `handleAgentDashboardRoute()` by returning
-`handleAgentDashboardRequest(request)`. Implement `src/server/index.ts` with
-Bun.serve routes for `/api/dashboard`, `/api/agent/dashboard`, static Vite
-assets, and SPA fallback to `index.html`.
-
-- [ ] **Step 4: Update React app to fetch `/api/dashboard`**
-
-`src/App.tsx` should export `AppStateView` for tests and make the default
-`App` fetch dashboard state with `useEffect`. It should clear refresh timers
-when state changes and on unmount.
-
-- [ ] **Step 5: Run route and app tests**
-
-Run:
-
-```sh
-bun run test:unit src/server/routes/dashboard.test.ts src/server/routes/agent-dashboard.test.ts src/App.test.tsx
-```
-
-Expected: PASS.
-
-- [ ] **Step 6: Commit**
-
-```sh
-git add src/server src/App.tsx src/App.test.tsx playwright.config.ts
-git commit -m "feat(server): add bun dashboard api"
-```
-
-## Task 3: React UI Component Library
-
-**Files:**
-- Create: `src/lib/ui/components/*.tsx`
-- Create: `src/lib/ui/components/styles.css`
-- Modify: `src/lib/ui/components/render.test.ts`
-- Modify: `src/lib/ui/index.ts`
-- Keep: `src/lib/ui/types.ts`
-- Keep: `src/lib/ui/model-renderer.ts`
-- Keep: `src/lib/ui/fixtures.ts`
-
-- [ ] **Step 1: Replace Svelte SSR tests with failing React render tests**
-
-Rewrite `src/lib/ui/components/render.test.ts` to import React components and
-`renderToString` from `react-dom/server`. Keep the current assertions for:
-
-- dashboard fixture content
-- generic secondary fixture content
-- optional service/status links
-- stable model IDs
-- progress bar behavior
-- uPlot chart surface marker
-- native attributes on Button and IconButton
-
-Run: `bun run test:unit src/lib/ui/components/render.test.ts`
-
-Expected: FAIL because React component files do not exist yet.
-
-- [ ] **Step 2: Port atomic components**
-
-Create React equivalents for `Badge`, `Button`, `IconGlyph`, `IconButton`,
-`ProgressMeter`, `Separator`, `Sparkline`, `SignalTrace`, `LineChart`, and
-`StatusBadge`. Preserve class names and `data-*` attributes from Svelte.
-
-Run: `bun run test:unit src/lib/ui/components/render.test.ts`
-
-Expected: remaining FAILs only for dashboard composite components.
-
-- [ ] **Step 3: Port layout and card components**
-
-Create React equivalents for `Panel`, `CornerBracketFrame`, `GridFrame`,
-`DiagonalStripeField`, `ModuleCard`, `TelemetryCard`, `TelemetryGrid`,
-`FooterCell`, `FooterStatusCell`, and `StatusStrip`.
-
-Run: `bun run test:unit src/lib/ui/components/render.test.ts`
-
-Expected: remaining FAILs only for service/dashboard shell components.
-
-- [ ] **Step 4: Port service and dashboard shell components**
-
-Create React equivalents for `ServiceRow`, `ServicePanel`,
-`ServiceGroupPanel`, `SystemState`, `DashboardHeader`, `DashboardFrame`,
-`TelemetryStrip`, and `WeatherModule`.
-
-Run: `bun run test:unit src/lib/ui/components/render.test.ts`
-
-Expected: PASS.
-
-- [ ] **Step 5: Export React components**
-
-Update `src/lib/ui/index.ts` to export `.tsx` React components and continue
-exporting fixtures, renderer, and UI types.
-
-Run:
-
-```sh
-bun run test:unit src/lib/ui/components/render.test.ts src/lib/ui/model-renderer.test.ts src/lib/ui/content-boundary.test.ts
-```
-
-Expected: PASS.
-
-- [ ] **Step 6: Commit**
-
-```sh
-git add src/lib/ui/components src/lib/ui/index.ts
-git commit -m "feat(ui): port dashboard components to react"
-```
-
-## Task 4: React Dashboard App Rendering
-
-**Files:**
-- Modify: `src/App.tsx`
-- Modify: `src/App.test.tsx`
-- Modify: `src/app.css`
-- Delete after `src/page.test.tsx` passes: `src/routes/page.test.ts`
-- Create: `src/page.test.tsx`
-
-- [ ] **Step 1: Write failing React page tests**
-
-Create `src/page.test.tsx` with React `renderToString` assertions equivalent to
-the current Svelte `src/routes/page.test.ts`:
-
-- ready dashboard renders model content
-- invalid model state renders validation errors
-- empty and loading states render without crashing
-
-Run: `bun run test:unit src/page.test.tsx`
-
-Expected: FAIL until `AppStateView` uses the React `DashboardFrame` and
-`SystemState` components.
-
-- [ ] **Step 2: Implement app state rendering**
-
-Use `dashboardDocumentToUiDashboard()` and `DashboardFrame` for ready state.
-Use `SystemState` for empty/loading/invalid states. Preserve state shell CSS
-and validation error list markup.
-
-- [ ] **Step 3: Run page tests**
-
-Run: `bun run test:unit src/page.test.tsx src/App.test.tsx`
-
-Expected: PASS.
-
-- [ ] **Step 4: Commit**
-
-```sh
-git add src/App.tsx src/App.test.tsx src/page.test.tsx src/app.css
-git commit -m "feat(app): render dashboard with react"
-```
-
-## Task 5: React Storybook
-
-**Files:**
-- Modify: `.storybook/main.ts`
-- Modify: `.storybook/preview.ts`
-- Create: `src/lib/ui/stories/*.stories.tsx`
-- Delete after replacement: `src/lib/ui/stories/*.stories.svelte`
-- Delete after replacement: `src/lib/ui/stories/FocusPreview.svelte`
-- Modify: `src/lib/ui/storybook.test.ts`
-
-- [ ] **Step 1: Update storybook boundary test first**
-
-Change `src/lib/ui/storybook.test.ts` so it requires React `.stories.tsx`
-files and rejects `.stories.svelte` files.
-
-Run: `bun run test:unit src/lib/ui/storybook.test.ts`
-
-Expected: FAIL while Svelte stories still exist.
-
-- [ ] **Step 2: Configure React Storybook**
-
-Update `.storybook/main.ts` to use `@storybook/react-vite`, React story globs,
-and the existing addons. Update `.storybook/preview.ts` type imports to React
-Storybook while preserving global CSS, MSW setup, backgrounds, controls, and
-fullscreen layout.
-
-- [ ] **Step 3: Port stories to React**
-
-Create `.stories.tsx` files for each existing Svelte story. Import React
-components from `src/lib/ui` and generic story data from
-`src/lib/ui/stories/story-data.ts`.
-
-- [ ] **Step 4: Remove Svelte stories and run Storybook checks**
-
-Run:
-
-```sh
-bun run test:unit src/lib/ui/storybook.test.ts
-bun run build-storybook
-```
-
-Expected: PASS.
-
-- [ ] **Step 5: Commit**
-
-```sh
-git add .storybook src/lib/ui/stories src/lib/ui/storybook.test.ts
-git commit -m "feat(storybook): migrate stories to react"
-```
-
-## Task 6: Remove SvelteKit Runtime
-
-**Files:**
-- Delete: `svelte.config.js`
-- Delete: `src/app.html`
-- Delete: `src/routes/**`
-- Delete: all remaining `*.svelte`
-- Modify: `package.json`
-- Modify: `bun.lock`
-- Modify: `README.md`
-- Modify: `Containerfile`
-- Modify: `playwright.config.ts`
-- Modify: `src/lib/presentation-boundary.test.ts`
-
-- [ ] **Step 1: Write/adjust cleanup tests**
-
-Add assertions to presentation or storybook boundary tests that no `.svelte`
-files remain under `src/`.
-
-Run: `bun run test:unit src/lib/presentation-boundary.test.ts src/lib/ui/storybook.test.ts`
-
-Expected: FAIL while Svelte files remain.
-
-- [ ] **Step 2: Delete Svelte runtime and dependencies**
-
-Remove all Svelte files and Svelte dependencies. Run `bun install
---frozen-lockfile` only after `package.json` and `bun.lock` are consistent, or
-run `bun remove` commands to update both together:
-
-```sh
-bun remove @iconify/svelte @storybook/addon-svelte-csf @storybook/sveltekit @sveltejs/adapter-node @sveltejs/kit @sveltejs/vite-plugin-svelte svelte svelte-check
-```
-
-- [ ] **Step 3: Update docs, container, and e2e build command**
-
-README should describe React, Vite, Bun server, and unchanged persistence.
-`Containerfile` should copy the Vite client output and Bun server output.
-`playwright.config.ts` should build and start the Bun server.
-
-- [ ] **Step 4: Run cleanup checks**
-
-Run:
-
-```sh
-rg -n "\\.svelte|svelte" package.json src .storybook vite.config.ts tsconfig.json README.md Containerfile
-bun run check
-bun run test:unit
-```
-
-Expected: `rg` finds no Svelte app/runtime references except historical docs in
-the committed design/plan, and checks pass.
-
-- [ ] **Step 5: Commit**
-
-```sh
-git add -A
-git commit -m "refactor: remove svelte runtime"
-```
-
-## Task 7: QA Gate, PR, Review, And Merge
-
-**Files:**
-- Modify only files needed to fix failures found by this task.
-
-- [ ] **Step 1: Run full QA gate**
-
-Run: `bun run test:qa`
-
-Expected: PASS for check, unit tests, production build, Storybook build, and
-Playwright desktop/mobile tests.
-
-- [ ] **Step 2: Inspect current diff**
-
-Run:
-
-```sh
-git status --short
-git diff --stat main...HEAD
-git diff --name-only main...HEAD
-```
-
-Expected: only React migration files and docs changed.
-
-- [ ] **Step 3: Push and open ready PR**
-
-Run:
-
-```sh
-git push -u origin codex/react-migration
-```
-
-Open a ready PR against `main` with title:
-
-```text
-refactor: migrate dashboard runtime to react
-```
-
-- [ ] **Step 4: Independent review**
-
-Dispatch an independent reviewer to inspect the issue goal, spec, plan, and PR
-diff in code-review mode. Blocking findings must be fixed on the same branch.
-
-- [ ] **Step 5: Fix review findings and re-run QA**
-
-For each blocking finding, write or update the relevant failing test first,
-make the minimal fix, and run the focused test plus `bun run test:qa`.
-
-- [ ] **Step 6: Merge only after green checks and no blocking review findings**
-
-Merge the PR into `main`, sync the worktree back to `main`, and mark the goal
-complete only after the completion criteria in the spec are proven by current
-state.
-
-## Plan Self-Review
-
-- Spec coverage: Tasks cover React scaffold, Bun API server, UI component
- migration, app rendering, Storybook migration, Svelte removal, QA, PR,
- independent review, and merge.
-- Red-flag scan: The plan has no incomplete-work markers and no unspecified
- acceptance gates.
-- Type consistency: Public names used across tasks are `AppStateView`,
- `loadDashboardResponse`, and `handleAgentDashboardRoute`.
diff --git a/docs/superpowers/plans/2026-06-20-turbo-component-library.md b/docs/superpowers/plans/2026-06-20-turbo-component-library.md
deleted file mode 100644
index 22d2ad8..0000000
--- a/docs/superpowers/plans/2026-06-20-turbo-component-library.md
+++ /dev/null
@@ -1,436 +0,0 @@
-# Turbo Component Library Implementation Plan
-
-> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
-
-**Goal:** Convert the single-package Dimension Lab website into a Turborepo workspace where `apps/web` consumes a compiled reusable React component library from `packages/ui`.
-
-**Architecture:** The root becomes a private Bun workspace with Turbo orchestration. `packages/ui` owns generic reusable UI components, CSS, theme helpers, Storybook, and package-level tests. `apps/web` owns the website runtime, model/server/database logic, the model-to-UI adapter, e2e tests, and deployment container.
-
-**Tech Stack:** Bun workspaces, Turborepo, Vite, React 19, TypeScript, Storybook React Vite, Tailwind CSS v4, shadcn CSS, Vitest, Playwright, Drizzle ORM, Bun SQLite.
-
----
-
-## File Structure
-
-- Create root `turbo.json`: cacheable task graph for `build`, `check`, `test:unit`, `build-storybook`, `test:e2e`, and `test:qa`.
-- Create root `tsconfig.base.json`: shared strict TypeScript defaults.
-- Modify root `package.json`: private workspace root with `apps/*` and `packages/*`, Turbo scripts, and `turbo` dev dependency only.
-- Create `packages/ui/package.json`: compiled `@dimensionlab/ui` package with code and CSS exports.
-- Create `packages/ui/tsconfig.json` and `packages/ui/tsconfig.build.json`: package typecheck and declaration/JS build config.
-- Create `packages/ui/src/styles.css`: library style entry importing font, uPlot CSS, tokens, and component CSS.
-- Move `src/lib/ui/components/**` to `packages/ui/src/components/**`.
-- Move `src/lib/ui/stories/**` to `packages/ui/src/stories/**`.
-- Move `src/lib/ui/tokens.css` to `packages/ui/src/tokens.css`.
-- Move `src/lib/ui/theme.ts`, `types.ts`, `format.ts`, `fixtures.ts`, and `index.ts` to `packages/ui/src/**`.
-- Move `src/lib/ui/components/styles.css` to `packages/ui/src/components/styles.css`.
-- Move `.storybook/**` to `packages/ui/.storybook/**`.
-- Move unused shadcn primitives from `src/lib/components/ui/**` to `packages/ui/src/primitives/**` and update their `cn` import to package-local `src/utils.ts`.
-- Move `src/lib/utils.ts` to `packages/ui/src/utils.ts`.
-- Move app runtime files into `apps/web`: `src/App.tsx`, `src/main.tsx`, `src/app.css`, `src/server/**`, `src/lib/model/**`, `src/lib/server/**`, `src/lib/testing/**`, `src/vite-env.d.ts`, `src/page.test.tsx`, `src/server/dev.ts`, `tests/**`, `drizzle/**`, `Containerfile`, `index.html`, `playwright.config.ts`, `vite.config.ts`, `drizzle.config.ts`, and app-specific README/deployment files.
-- Move `src/lib/ui/model-renderer.ts` and `model-renderer.test.ts` into `apps/web/src/lib/ui-adapter/**`.
-- Create `apps/web/package.json`, `apps/web/tsconfig.json`, `apps/web/vite.config.ts`, and `apps/web/playwright.config.ts`.
-- Update `apps/web/src/App.tsx` to import components/types from `@dimensionlab/ui` and import the adapter from `$lib/ui-adapter/model-renderer`.
-- Update `apps/web/src/app.css` to import `@dimensionlab/ui/styles.css` instead of local UI CSS files.
-- Update tests that read paths so package boundary tests inspect `packages/ui` and app tests inspect `apps/web`.
-- Update `README.md` to describe the workspace commands, package boundaries, Storybook location, and deployment path.
-
-## Task 1: Workspace And Boundary Tests
-
-**Files:**
-- Modify: `package.json`
-- Create: `turbo.json`
-- Create: `tsconfig.base.json`
-- Create: `packages/ui/package.json`
-- Create: `packages/ui/tsconfig.json`
-- Create: `packages/ui/tsconfig.build.json`
-- Create: `apps/web/package.json`
-- Create: `apps/web/tsconfig.json`
-- Create: `apps/web/src/lib/workspace-boundary.test.ts`
-
-- [ ] **Step 1: Write the failing workspace boundary test**
-
-Create `apps/web/src/lib/workspace-boundary.test.ts`:
-
-```ts
-import { existsSync, readFileSync } from "node:fs";
-import { join } from "node:path";
-import { describe, expect, test } from "vitest";
-
-const root = join(import.meta.dir, "../../../..");
-
-describe("workspace boundaries", () => {
- test("declares the root as a turbo-managed bun workspace", () => {
- const packageJson = JSON.parse(readFileSync(join(root, "package.json"), "utf8")) as {
- private?: boolean;
- scripts?: Record;
- workspaces?: string[];
- };
-
- expect(packageJson.private).toBe(true);
- expect(packageJson.workspaces).toEqual(["apps/*", "packages/*"]);
- expect(packageJson.scripts?.build).toBe("turbo build");
- expect(existsSync(join(root, "turbo.json"))).toBe(true);
- });
-
- test("keeps the website app and reusable UI library as separate packages", () => {
- const webPackage = JSON.parse(
- readFileSync(join(root, "apps/web/package.json"), "utf8"),
- ) as { dependencies?: Record; name?: string };
- const uiPackage = JSON.parse(
- readFileSync(join(root, "packages/ui/package.json"), "utf8"),
- ) as { exports?: Record; name?: string };
-
- expect(webPackage.name).toBe("@dimensionlab/web");
- expect(webPackage.dependencies?.["@dimensionlab/ui"]).toBe("workspace:*");
- expect(uiPackage.name).toBe("@dimensionlab/ui");
- expect(uiPackage.exports).toHaveProperty(".");
- expect(uiPackage.exports).toHaveProperty("./styles.css");
- });
-});
-```
-
-- [ ] **Step 2: Run the test to verify it fails**
-
-Run:
-
-```sh
-bunx vitest run apps/web/src/lib/workspace-boundary.test.ts
-```
-
-Expected: FAIL because `apps/web`, `packages/ui`, and `turbo.json` do not exist.
-
-- [ ] **Step 3: Add minimal workspace manifests**
-
-Create root `package.json` as the workspace orchestrator:
-
-```json
-{
- "name": "dimensionlab",
- "version": "0.0.1",
- "private": true,
- "type": "module",
- "packageManager": "bun@1.3.14",
- "workspaces": ["apps/*", "packages/*"],
- "scripts": {
- "dev": "turbo dev --filter=@dimensionlab/web",
- "build": "turbo build",
- "preview": "bun run --cwd apps/web preview",
- "storybook": "turbo storybook --filter=@dimensionlab/ui",
- "build-storybook": "turbo build-storybook --filter=@dimensionlab/ui",
- "check": "turbo check",
- "test": "turbo test:unit",
- "test:unit": "turbo test:unit",
- "test:e2e": "turbo test:e2e --filter=@dimensionlab/web",
- "test:qa": "turbo test:qa",
- "db:generate": "bun run --cwd apps/web db:generate",
- "db:check": "bun run --cwd apps/web db:check"
- },
- "devDependencies": {
- "turbo": "^2.5.0"
- }
-}
-```
-
-Create `turbo.json`:
-
-```json
-{
- "$schema": "https://turbo.build/schema.json",
- "tasks": {
- "build": {
- "dependsOn": ["^build"],
- "outputs": ["dist/**", "build/**"]
- },
- "check": {
- "dependsOn": ["^build"],
- "outputs": []
- },
- "test:unit": {
- "dependsOn": ["^build"],
- "outputs": []
- },
- "build-storybook": {
- "dependsOn": ["^build"],
- "outputs": ["storybook-static/**"]
- },
- "test:e2e": {
- "dependsOn": ["build", "^build"],
- "outputs": ["test-results/**", "playwright-report/**"]
- },
- "test:qa": {
- "dependsOn": ["check", "test:unit", "build", "build-storybook", "test:e2e"],
- "outputs": []
- },
- "dev": {
- "cache": false,
- "persistent": true
- },
- "storybook": {
- "cache": false,
- "persistent": true
- }
- }
-}
-```
-
-Create minimal `packages/ui/package.json` with `@dimensionlab/ui` exports and
-minimal `apps/web/package.json` with `@dimensionlab/ui` as a workspace
-dependency. Move the full dependency lists in Task 2 and Task 3.
-
-- [ ] **Step 4: Run the boundary test**
-
-Run:
-
-```sh
-bunx vitest run apps/web/src/lib/workspace-boundary.test.ts
-```
-
-Expected: PASS.
-
-- [ ] **Step 5: Commit**
-
-```sh
-git add package.json turbo.json tsconfig.base.json apps/web/package.json apps/web/tsconfig.json apps/web/src/lib/workspace-boundary.test.ts packages/ui/package.json packages/ui/tsconfig.json packages/ui/tsconfig.build.json
-git commit -m "build: add turbo workspace manifests"
-```
-
-## Task 2: Extract The UI Package
-
-**Files:**
-- Move: `src/lib/ui/components/**` to `packages/ui/src/components/**`
-- Move: `src/lib/ui/stories/**` to `packages/ui/src/stories/**`
-- Move: `src/lib/ui/{index.ts,types.ts,theme.ts,format.ts,fixtures.ts,tokens.css}` to `packages/ui/src/**`
-- Move: `.storybook/**` to `packages/ui/.storybook/**`
-- Move: `src/lib/components/ui/**` to `packages/ui/src/primitives/**`
-- Move: `src/lib/utils.ts` to `packages/ui/src/utils.ts`
-- Create: `packages/ui/src/styles.css`
-- Modify: `packages/ui/src/index.ts`
-- Modify: `packages/ui/src/storybook.test.ts`
-- Modify: `packages/ui/src/content-boundary.test.ts`
-
-- [ ] **Step 1: Write the failing UI isolation assertion**
-
-Update the package boundary tests to read from `packages/ui/src` and assert no
-imports from `apps/web`, `$lib/server`, or `$lib/model` exist:
-
-```ts
-expect(source).not.toMatch(/from ["'](?:apps\/web|\$lib\/server|\$lib\/model)/);
-```
-
-Run:
-
-```sh
-bunx vitest run packages/ui/src/content-boundary.test.ts
-```
-
-Expected: FAIL until the files move and the app-specific adapter is removed.
-
-- [ ] **Step 2: Move generic UI files**
-
-Run mechanical moves with `git mv`. Move `model-renderer.ts` out of the UI
-package in Task 3 rather than into `packages/ui`.
-
-- [ ] **Step 3: Add the UI style entry**
-
-Create `packages/ui/src/styles.css`:
-
-```css
-@import "@fontsource-variable/geist";
-@import "uplot/dist/uPlot.min.css";
-@import "./tokens.css";
-@import "./components/styles.css";
-```
-
-- [ ] **Step 4: Make package imports relative or package-local**
-
-Update moved primitive files to import `cn` from `../utils`.
-Remove `dashboardDocumentToUiDashboard` from `packages/ui/src/index.ts`.
-
-- [ ] **Step 5: Build the package**
-
-Run:
-
-```sh
-bun run --cwd packages/ui build
-bun run --cwd packages/ui check
-bun run --cwd packages/ui test:unit
-```
-
-Expected: PASS and `packages/ui/dist` contains `index.js`, `index.d.ts`, and
-CSS files.
-
-- [ ] **Step 6: Commit**
-
-```sh
-git add packages/ui src/lib/ui src/lib/components src/lib/utils.ts
-git commit -m "refactor(ui): extract reusable component package"
-```
-
-## Task 3: Move The Web App Workspace
-
-**Files:**
-- Move: `src/**` app files that are not reusable UI to `apps/web/src/**`
-- Move: `tests/**` to `apps/web/tests/**`
-- Move: `drizzle/**` to `apps/web/drizzle/**`
-- Move: `Containerfile` to `apps/web/Containerfile`
-- Move: `index.html`, `vite.config.ts`, `playwright.config.ts`, `drizzle.config.ts` to `apps/web/**`
-- Create: `apps/web/src/lib/ui-adapter/model-renderer.ts`
-- Modify: `apps/web/src/App.tsx`
-- Modify: `apps/web/src/app.css`
-
-- [ ] **Step 1: Move the model adapter out of UI**
-
-Move `src/lib/ui/model-renderer.ts` to
-`apps/web/src/lib/ui-adapter/model-renderer.ts` and update imports from
-`$lib/model` plus UI types from `@dimensionlab/ui`.
-
-- [ ] **Step 2: Move app runtime and tests**
-
-Use `git mv` for app/server/model/test/deployment files into `apps/web`.
-
-- [ ] **Step 3: Update app imports**
-
-In `apps/web/src/App.tsx`, import UI components and types from
-`@dimensionlab/ui`, and import `dashboardDocumentToUiDashboard` from
-`$lib/ui-adapter/model-renderer`.
-
-In `apps/web/src/app.css`, replace local UI imports with:
-
-```css
-@import "tailwindcss";
-@import "tw-animate-css";
-@import "shadcn/tailwind.css";
-@import "@dimensionlab/ui/styles.css";
-```
-
-- [ ] **Step 4: Run app typecheck and unit tests**
-
-Run:
-
-```sh
-bun run --cwd apps/web check
-bun run --cwd apps/web test:unit
-```
-
-Expected: PASS.
-
-- [ ] **Step 5: Commit**
-
-```sh
-git add apps/web src tests drizzle Containerfile index.html vite.config.ts playwright.config.ts drizzle.config.ts
-git commit -m "refactor(web): move website into app workspace"
-```
-
-## Task 4: Wire Turbo, Storybook, Playwright, And Container
-
-**Files:**
-- Modify: `apps/web/playwright.config.ts`
-- Modify: `apps/web/Containerfile`
-- Modify: `apps/web/vite.config.ts`
-- Modify: `packages/ui/.storybook/main.ts`
-- Modify: `packages/ui/.storybook/preview.ts`
-- Modify: `tests/e2e/storybook-server.ts` after move to `apps/web/tests/e2e/storybook-server.ts`
-- Modify: `README.md`
-
-- [ ] **Step 1: Update Playwright commands for workspaces**
-
-In `apps/web/playwright.config.ts`, make the web server command build from the
-workspace root or app directory consistently:
-
-```ts
-command: `DISABLE_LIVE_DATASOURCES=1 bun run build && DISABLE_LIVE_DATASOURCES=1 DATABASE_URL=${databaseUrl} HOST=127.0.0.1 PORT=${port} bun build/index.js`
-```
-
-This command runs inside `apps/web` when invoked through the app package script.
-
-- [ ] **Step 2: Update Storybook package paths**
-
-`packages/ui/.storybook/main.ts` should use `../src/**/*.stories.@(js|ts|tsx)`.
-`packages/ui/.storybook/preview.ts` should import `../src/styles.css` and use
-generic MSW handlers only if they are moved into the UI package.
-
-- [ ] **Step 3: Update the container build**
-
-`apps/web/Containerfile` should build from the repository root context or copy
-only the workspace files it needs. Preserve the runtime command:
-
-```dockerfile
-CMD ["bun", "build/index.js"]
-```
-
-- [ ] **Step 4: Run full root checks**
-
-Run:
-
-```sh
-bun install
-bun run check
-bun run test:unit
-bun run build
-bun run build-storybook
-bun run test:e2e
-```
-
-Expected: PASS.
-
-- [ ] **Step 5: Commit**
-
-```sh
-git add README.md apps/web packages/ui package.json turbo.json bun.lock
-git commit -m "build: wire turbo workspace qa"
-```
-
-## Task 5: Final QA, PR, Review, Merge, Deploy
-
-**Files:**
-- No planned source edits unless verification finds blockers.
-
-- [ ] **Step 1: Run release gate**
-
-Run:
-
-```sh
-bun run test:qa
-```
-
-Expected: PASS.
-
-- [ ] **Step 2: Push and open PR**
-
-Run:
-
-```sh
-git push -u origin codex/turbo-component-library
-```
-
-Open a ready PR against `main` titled:
-
-```text
-refactor: migrate dashboard to turbo component library
-```
-
-- [ ] **Step 3: Independent review**
-
-Send an independent reviewer to inspect the PR diff against the objective:
-Turbo repo, `apps/web`, compiled `packages/ui`, reusable components removed
-from app, Storybook with UI package, root QA passing, no server behavior change.
-
-- [ ] **Step 4: Resolve blockers**
-
-For each blocking review finding, write or update a failing test first, verify
-the failure, implement the fix, run targeted checks, commit, push, and re-review.
-
-- [ ] **Step 5: Merge and deploy**
-
-When review and checks are clean, merge the PR into `main`, sync the production
-checkout, rebuild the Podman image, restart `dimensionlab-website.service`, and
-verify `https://dimensionlab.net/` with a browser smoke check.
-
-## Self-Review
-
-- Spec coverage: every completion criterion in the design maps to Task 1 through
- Task 5.
-- Placeholder scan: no task says TBD, TODO, or "add tests" without commands.
-- Type consistency: package names are consistently `@dimensionlab/ui` and
- `@dimensionlab/web`; the app adapter path is consistently
- `$lib/ui-adapter/model-renderer`.
diff --git a/docs/superpowers/specs/2026-06-19-react-runtime-migration-design.md b/docs/superpowers/specs/2026-06-19-react-runtime-migration-design.md
deleted file mode 100644
index 17a8fa6..0000000
--- a/docs/superpowers/specs/2026-06-19-react-runtime-migration-design.md
+++ /dev/null
@@ -1,177 +0,0 @@
-# React Runtime Migration Design
-
-## Context
-
-The current Dimension Lab website is a SvelteKit application. It uses Bun,
-Vite, Svelte 5, SvelteKit server routes, Svelte Storybook stories, and
-Svelte SSR component tests. The app has one dashboard route, one agent
-configuration API route, reusable Svelte UI components, typed dashboard model
-data, Drizzle-backed SQLite persistence, datasource resolution, Playwright
-desktop/mobile checks, and a Bun-based container runtime.
-
-The migration goal is to make the project React-based so dashboard UI
-components can be reused outside the current app. A partial React island inside
-SvelteKit would leave the app split across two component systems, so the target
-state is a React runtime and React component library.
-
-## Target Architecture
-
-Use Vite React for the browser app and a small Bun HTTP server for production
-runtime. This keeps the existing Bun SQLite persistence model and avoids adding
-a heavier React framework where the current route/API surface is small. Use
-Tailwind CSS v4 and shadcn/ui as the reusable primitive layer for React
-components, but do not vendor the full shadcn registry. Add only primitives
-that map to the dashboard surface.
-
-The migrated app will have these boundaries:
-
-- `src/main.tsx` mounts the React application in the browser.
-- `src/App.tsx` owns dashboard loading, refresh timing, document-to-UI mapping,
- and non-ready dashboard states.
-- `src/server/index.ts` serves the Vite build output in production and exposes
- JSON API routes.
-- `src/server/routes/dashboard.ts` loads the dashboard runtime and resolves live
- datasources unless `DISABLE_LIVE_DATASOURCES=1`.
-- `src/server/routes/agent-dashboard.ts` delegates POST requests to the existing
- `handleAgentDashboardRequest` function.
-- `src/lib/model/**`, `src/lib/server/db/**`, `src/lib/server/dashboard.ts`,
- `src/lib/server/datasources/**`, and `src/lib/server/agent-config/**` remain
- TypeScript business logic with minimal import-path updates.
-- `src/lib/ui/components/*.tsx` contains the reusable React component library.
-- `src/lib/ui/stories/*.stories.tsx` contains React Storybook stories.
-- `src/lib/components/ui/*.tsx` contains shadcn/ui primitives used by the
- dashboard component library.
-- `components.json` records the shadcn configuration with Vite, Radix, the Nova
- preset, and `$lib` import aliases.
-
-## Component Migration
-
-Every current Svelte UI component will be ported to React with typed props:
-
-- Badge
-- Button
-- CornerBracketFrame
-- DashboardFrame
-- DashboardHeader
-- DiagonalStripeField
-- FooterCell
-- FooterStatusCell
-- GridFrame
-- IconButton
-- IconGlyph
-- LineChart
-- ModuleCard
-- Panel
-- ProgressMeter
-- ScanlineField
-- Separator
-- ServiceGroupPanel
-- ServicePanel
-- ServiceRow
-- SignalTrace
-- Sparkline
-- StatusBadge
-- StatusStrip
-- SystemState
-- TelemetryCard
-- TelemetryGrid
-- TelemetryStrip
-- WeatherModule
-
-The visual design, CSS custom property tokens, accessibility attributes,
-data-model identifiers, severity attributes, focusable links, reduced-motion
-behavior, and screenshot-tested dashboard layout must stay equivalent to the
-current Svelte implementation.
-
-CSS will keep the existing design tokens in `src/lib/ui/tokens.css`.
-`src/app.css` imports Tailwind, shadcn CSS, font assets, and the existing
-Dimension Lab token file. shadcn semantic variables must be mapped to the dark
-console palette so generated primitives fit the dashboard instead of resetting
-the app to a light generic theme. Shared UI types and
-`dashboardDocumentToUiDashboard` remain framework-agnostic TypeScript.
-
-## Runtime And API Behavior
-
-The React app will fetch `GET /api/dashboard` on page load. When the runtime
-state is ready, the response includes the dashboard document and metadata. When
-the runtime state is empty, loading, or invalid, the React app renders the same
-state shell that the Svelte page currently renders.
-
-Ready dashboard documents use `metadata.refreshIntervalSeconds` to schedule a
-refresh. The React implementation will clear old timers when the dashboard
-state changes and on unmount.
-
-The existing agent configuration endpoint remains available at
-`POST /api/agent/dashboard`. The request validation, token authorization,
-preview, publish, rollback, JSON patch behavior, and persistence behavior remain
-owned by `src/lib/server/agent-config/index.ts`.
-
-## Build, Storybook, And Deployment
-
-`package.json` will move from Svelte/SvelteKit dependencies to React tooling:
-
-- Runtime dependencies include `react`, `react-dom`, `@iconify/react`,
- selected shadcn primitive dependencies, Tailwind merge helpers, and existing
- non-Svelte libraries that still apply.
-- Dev dependencies include `@vitejs/plugin-react`, `@tailwindcss/vite`,
- `tailwindcss`, and the shadcn package needed by the generated CSS import.
-- Storybook moves from `@storybook/sveltekit` and Svelte CSF to
- `@storybook/react-vite`.
-- `svelte.config.js`, `src/app.html`, `src/routes/**`, and `.svelte` files are
- removed after equivalent React/server files exist.
-
-The production build still creates `build/index.js` as the Bun server entry so
-the current container command remains:
-
-```sh
-DATABASE_URL=file:/data/dimensionlab.sqlite HOST=0.0.0.0 PORT=3000 bun build/index.js
-```
-
-The `Containerfile` continues to install with Bun, build with Bun, copy the
-client/server build output plus `drizzle/`, and run the Bun server.
-
-## Testing Strategy
-
-The migration is verified with equivalent or stronger tests:
-
-- TypeScript check covers React TSX, server modules, and shared model code.
-- Current model, validation, database, datasource, and agent-config unit tests
- remain in place.
-- Svelte SSR component tests become React `react-dom/server` tests.
-- Page rendering tests become React app/server response tests.
-- Storybook boundary tests are updated to require React story files and continue
- preventing Dimension Lab-specific content in reusable presentation stories.
-- Playwright desktop/mobile tests continue to run against the production Bun
- server and the built React app.
-- The full QA gate remains `bun run test:qa`.
-
-## Completion Criteria
-
-The migration is complete only when current evidence proves all of these:
-
-- There are no `.svelte` app, component, route, or story files left.
-- `package.json` has no Svelte, SvelteKit, or Svelte Storybook dependencies.
-- shadcn is configured for Vite/Radix with `$lib` aliases and only selected
- primitives, not the full registry.
-- `bun run check` passes.
-- `bun run test:unit` passes.
-- `bun run build` produces the React client build and Bun server entry.
-- `bun run build-storybook` passes with React stories.
-- `bun run test:e2e` passes on desktop and mobile.
-- The dashboard renders from the same validated dashboard model data.
-- `POST /api/agent/dashboard` still exercises the existing agent config logic.
-- The production container still runs with `bun build/index.js`.
-
-## Migration Approach
-
-The work should be implemented in focused commits on `codex/react-migration`:
-
-1. Establish React/Vite/Bun server scaffolding and tests while keeping the
- current Svelte code available for reference.
-2. Port reusable UI components to React and update render tests.
-3. Port the dashboard app route and refresh behavior to React.
-4. Port Storybook stories and presentation boundary tests to React.
-5. Remove SvelteKit runtime files and dependencies.
-6. Update build, e2e, README, and container behavior.
-7. Run the full QA gate, push the branch, open a ready PR, perform independent
- review, fix blockers, and merge only after checks and review pass.
diff --git a/docs/superpowers/specs/2026-06-20-turbo-component-library-design.md b/docs/superpowers/specs/2026-06-20-turbo-component-library-design.md
deleted file mode 100644
index ba0fefc..0000000
--- a/docs/superpowers/specs/2026-06-20-turbo-component-library-design.md
+++ /dev/null
@@ -1,186 +0,0 @@
-# Turbo Component Library Migration Design
-
-## Context
-
-The current Dimension Lab website is a single Bun/Vite React package. It owns
-the browser app, Bun production server, dashboard model, persistence, datasource
-adapters, reusable UI components, Storybook, Playwright checks, and container
-deployment from one `package.json`.
-
-The UI components are already mostly generic and content-free under
-`src/lib/ui`, but they are not reusable by another project because they live
-inside the app package, depend on app path aliases, and share the app build,
-test, and Storybook configuration. One file in that area,
-`src/lib/ui/model-renderer.ts`, imports the Dimension Lab dashboard model and
-therefore is an app adapter, not reusable component-library code.
-
-The migration goal is to turn the repository into a Turborepo workspace where
-the Dimension Lab website consumes a separate reusable React component package.
-
-## Target Repository Shape
-
-Use one application workspace and one component-library workspace:
-
-```text
-.
-├── apps/
-│ └── web/
-│ ├── src/
-│ ├── tests/
-│ ├── drizzle/
-│ ├── Containerfile
-│ └── package.json
-├── packages/
-│ └── ui/
-│ ├── src/
-│ ├── .storybook/
-│ ├── package.json
-│ └── tsconfig.json
-├── package.json
-├── turbo.json
-├── tsconfig.base.json
-└── bun.lock
-```
-
-The root package is private and contains only workspace orchestration:
-workspaces, Turbo scripts, shared dev dependencies where useful, and the lock
-file. Runtime dependencies belong to the workspace that imports them.
-
-## Package Ownership
-
-`packages/ui` is a compiled React library named `@dimensionlab/ui`.
-
-It owns:
-
-- Reusable React components currently under `src/lib/ui/components`.
-- UI CSS tokens and component styles.
-- Theme helpers currently under `src/lib/ui/theme.ts`.
-- Generic UI prop/data types currently under `src/lib/ui/types.ts`.
-- Generic formatting helpers currently under `src/lib/ui/format.ts`.
-- Generic Storybook stories and story fixtures.
-- UI render tests, boundary tests, and Storybook inventory tests.
-
-It must not import from the website app, the dashboard model, server modules,
-database modules, datasource modules, or deployment files.
-
-The library publishes explicit package exports:
-
-- `@dimensionlab/ui` for component and type exports.
-- `@dimensionlab/ui/styles.css` for the combined token/component CSS entry.
-- Optional explicit subpath exports for future direct imports where useful.
-
-The compiled output goes to `packages/ui/dist` and includes JavaScript,
-declaration files, and copied CSS. The package stays private for now but is
-structured so it can later be published or moved into another Dimension Lab repo
-without taking the website runtime with it.
-
-`apps/web` owns:
-
-- The Vite React browser app.
-- The Bun production server and API routes.
-- Dashboard model, fixtures, schema, validation, and model migrations.
-- Drizzle/SQLite persistence and checked-in SQL migrations.
-- Datasource adapters and runtime dashboard loading.
-- Agent dashboard configuration endpoint.
-- The `dashboardDocumentToUiDashboard` adapter that maps the app model to UI
- package props.
-- Playwright e2e tests and deployment container.
-
-## Why A Compiled Package
-
-The component package should be compiled rather than a just-in-time source
-package. This is slightly more setup, but it better fits reuse outside the
-current app because consumers can import stable JavaScript and declarations
-instead of relying on their bundler to transpile this repo's TypeScript source.
-It also gives Turbo a cacheable `@dimensionlab/ui#build` task.
-
-## Build And Task Graph
-
-Root scripts delegate through Turbo:
-
-- `bun run dev` runs the web dev server and any required dependency tasks.
-- `bun run build` runs package builds in dependency order.
-- `bun run check` runs TypeScript checks for all workspaces.
-- `bun run test:unit` runs Vitest unit tests for all workspaces.
-- `bun run build-storybook` builds Storybook from `packages/ui`.
-- `bun run test:e2e` runs the web app Playwright suite.
-- `bun run test:qa` is the full release gate.
-
-`turbo.json` defines `build`, `check`, `test:unit`, `build-storybook`,
-`test:e2e`, and `test:qa` tasks. Build outputs include `dist/**`,
-`storybook-static/**`, and `build/**` as appropriate.
-
-The web app depends on `@dimensionlab/ui` using Bun workspace syntax. The web
-Vite config aliases `$lib` to `apps/web/src/lib`; the UI package should not use
-that app alias.
-
-## Storybook
-
-Storybook moves with the component package. It should load
-`@dimensionlab/ui/styles.css`, use React Vite Storybook, and keep the existing
-generic story inventory. Environment-specific Dimension Lab labels, hostnames,
-links, fallback values, and datasource names remain forbidden in package UI
-source and stories.
-
-The repository should no longer have a root Storybook tied to the web app.
-
-## Deployment
-
-The deployed website remains the same service from the outside:
-
-- Production command remains `bun build/index.js` inside the runtime image.
-- The container still exposes port `3000` and mounts `/data`.
-- Runtime environment variables and database behavior remain unchanged.
-
-The `Containerfile` moves to `apps/web/Containerfile` or remains root with
-updated workspace-aware copy/build steps. The chosen layout must preserve the
-existing Podman service contract used by `dimensionlab-website.service`.
-
-## Testing Strategy
-
-The migration must add or update tests that prove the new boundaries:
-
-- The root package is a Bun workspace with `apps/*` and `packages/*`.
-- The web app imports UI from `@dimensionlab/ui`, not from local copied
- component files.
-- `packages/ui` does not import from `apps/web`, `$lib/server`, `$lib/model`,
- or any website runtime module.
-- `dashboardDocumentToUiDashboard` lives in `apps/web` and is tested there.
-- Storybook inventory is evaluated against `packages/ui`.
-- The full QA gate still covers typecheck, unit tests, app build, Storybook
- build, and Playwright desktop/mobile checks.
-
-## Completion Criteria
-
-The migration is complete only when current evidence proves all of these:
-
-- Root `package.json` is a private workspace root with Turbo scripts.
-- `turbo.json` exists and models the workspace task graph.
-- The web application lives under `apps/web`.
-- The reusable React component library lives under `packages/ui`.
-- `packages/ui/package.json` is named `@dimensionlab/ui` and has compiled
- exports for code, types, and CSS.
-- The web app depends on `@dimensionlab/ui` through the workspace.
-- Reusable components and Storybook have been removed from the web app package.
-- App-specific model/server/database/datasource code has not moved into
- `packages/ui`.
-- `dashboardDocumentToUiDashboard` is outside the UI package.
-- `bun run check` passes from the root.
-- `bun run test:unit` passes from the root.
-- `bun run build` passes from the root.
-- `bun run build-storybook` passes from the root.
-- `bun run test:e2e` passes from the root.
-- The production container can still be built and run with the same service
- contract.
-
-## Migration Approach
-
-Implement this on branch `codex/turbo-component-library` in focused commits:
-
-1. Add the workspace/Turbo scaffolding and boundary tests.
-2. Move UI code and Storybook to `packages/ui`.
-3. Move the app runtime into `apps/web` and wire it to `@dimensionlab/ui`.
-4. Move the model-to-UI adapter into the web app.
-5. Update build, test, Playwright, Storybook, README, and container paths.
-6. Run the full QA gate, push a ready PR, perform independent review, resolve
- blockers, merge to `main`, and deploy only when checks and review are clean.
diff --git a/apps/web/drizzle.config.ts b/drizzle.config.ts
similarity index 100%
rename from apps/web/drizzle.config.ts
rename to drizzle.config.ts
diff --git a/apps/web/drizzle/0000_dashboard_persistence.sql b/drizzle/0000_dashboard_persistence.sql
similarity index 100%
rename from apps/web/drizzle/0000_dashboard_persistence.sql
rename to drizzle/0000_dashboard_persistence.sql
diff --git a/apps/web/drizzle/meta/0000_snapshot.json b/drizzle/meta/0000_snapshot.json
similarity index 100%
rename from apps/web/drizzle/meta/0000_snapshot.json
rename to drizzle/meta/0000_snapshot.json
diff --git a/apps/web/drizzle/meta/_journal.json b/drizzle/meta/_journal.json
similarity index 100%
rename from apps/web/drizzle/meta/_journal.json
rename to drizzle/meta/_journal.json
diff --git a/package.json b/package.json
index e33d3aa..e8a81a7 100644
--- a/package.json
+++ b/package.json
@@ -1,28 +1,42 @@
{
- "name": "dimensionlab",
+ "name": "dimensionlab-website",
"version": "0.0.1",
"private": true,
"type": "module",
- "packageManager": "bun@1.3.14",
- "workspaces": [
- "apps/*",
- "packages/*"
- ],
"scripts": {
- "dev": "turbo run dev --filter=@dimensionlab/web",
- "build": "turbo run build",
- "preview": "turbo run preview --filter=@dimensionlab/web",
- "storybook": "turbo run storybook --filter=@dimensionlab/ui",
- "build-storybook": "turbo run build-storybook --filter=@dimensionlab/ui",
- "check": "turbo run check",
- "test": "turbo run test:unit",
- "test:unit": "turbo run test:unit",
- "test:e2e": "turbo run test:e2e --filter=@dimensionlab/web",
- "test:qa": "turbo run check test:unit build @dimensionlab/ui#build-storybook @dimensionlab/web#test:e2e",
- "db:generate": "turbo run db:generate --filter=@dimensionlab/web",
- "db:check": "turbo run db:check --filter=@dimensionlab/web"
+ "dev": "vite --host 0.0.0.0",
+ "build": "vite build",
+ "preview": "vite preview --host 0.0.0.0",
+ "storybook": "storybook dev -p 6006 --host 0.0.0.0",
+ "build-storybook": "storybook build",
+ "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
+ "test": "vitest run",
+ "db:generate": "drizzle-kit generate",
+ "db:check": "drizzle-kit check"
+ },
+ "dependencies": {
+ "@iconify/svelte": "^5.2.2",
+ "@sinclair/typebox": "^0.34.49",
+ "ajv": "^8.20.0",
+ "ajv-formats": "^3.0.1",
+ "drizzle-orm": "^0.45.2"
},
"devDependencies": {
- "turbo": "^2.5.0"
+ "@storybook/addon-a11y": "^10.4.6",
+ "@storybook/addon-svelte-csf": "^5.1.2",
+ "@storybook/addon-vitest": "^10.4.6",
+ "@storybook/sveltekit": "^10.4.6",
+ "@sveltejs/adapter-node": "^5.5.4",
+ "@sveltejs/kit": "^2.65.2",
+ "@sveltejs/vite-plugin-svelte": "^7.1.2",
+ "@types/bun": "^1.3.14",
+ "@types/node": "^25.9.3",
+ "drizzle-kit": "^0.31.10",
+ "storybook": "^10.4.6",
+ "svelte": "^5.56.3",
+ "svelte-check": "^4.6.0",
+ "typescript": "^6.0.3",
+ "vite": "^8.0.16",
+ "vitest": "^4.1.9"
}
}
diff --git a/packages/dashboard-model/package.json b/packages/dashboard-model/package.json
deleted file mode 100644
index ae9a49b..0000000
--- a/packages/dashboard-model/package.json
+++ /dev/null
@@ -1,36 +0,0 @@
-{
- "name": "@dimensionlab/dashboard-model",
- "version": "0.0.1",
- "private": true,
- "type": "module",
- "exports": {
- ".": {
- "types": "./dist/index.d.ts",
- "development": "./src/index.ts",
- "default": "./dist/index.js"
- },
- "./fixtures": {
- "types": "./dist/fixtures/index.d.ts",
- "development": "./src/fixtures/index.ts",
- "default": "./dist/fixtures/index.js"
- }
- },
- "scripts": {
- "build": "rm -rf dist && tsc -p tsconfig.build.json",
- "check": "tsc --noEmit",
- "test": "vitest run",
- "test:unit": "vitest run"
- },
- "dependencies": {
- "@sinclair/typebox": "^0.34.49",
- "ajv": "^8.20.0",
- "ajv-formats": "^3.0.1"
- },
- "devDependencies": {
- "@types/bun": "^1.3.14",
- "@types/node": "^25.9.3",
- "bun-types": "^1.3.14",
- "typescript": "^6.0.3",
- "vitest": "^4.1.9"
- }
-}
diff --git a/packages/dashboard-model/src/fixtures/index.ts b/packages/dashboard-model/src/fixtures/index.ts
deleted file mode 100644
index f62d646..0000000
--- a/packages/dashboard-model/src/fixtures/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export { genericDashboardFixture } from "./generic";
diff --git a/packages/dashboard-model/tsconfig.build.json b/packages/dashboard-model/tsconfig.build.json
deleted file mode 100644
index 5d3e7ce..0000000
--- a/packages/dashboard-model/tsconfig.build.json
+++ /dev/null
@@ -1,12 +0,0 @@
-{
- "extends": "./tsconfig.json",
- "compilerOptions": {
- "declaration": true,
- "emitDeclarationOnly": false,
- "noEmit": false,
- "outDir": "dist",
- "rootDir": "src"
- },
- "include": ["src/**/*.ts"],
- "exclude": ["dist", "node_modules", "src/**/*.test.ts"]
-}
diff --git a/packages/dashboard-model/tsconfig.json b/packages/dashboard-model/tsconfig.json
deleted file mode 100644
index 26cb1c4..0000000
--- a/packages/dashboard-model/tsconfig.json
+++ /dev/null
@@ -1,9 +0,0 @@
-{
- "extends": "../../tsconfig.base.json",
- "compilerOptions": {
- "baseUrl": ".",
- "types": ["node", "bun-types"]
- },
- "include": ["src/**/*.ts"],
- "exclude": ["dist", "node_modules"]
-}
diff --git a/packages/ui b/packages/ui
deleted file mode 160000
index a7a4720..0000000
--- a/packages/ui
+++ /dev/null
@@ -1 +0,0 @@
-Subproject commit a7a472083555152b8e1a2dc018d3be5b3b10d60b
diff --git a/scripts/deploy-dimensionlab-website.sh b/scripts/deploy-dimensionlab-website.sh
deleted file mode 100755
index 4fac4cd..0000000
--- a/scripts/deploy-dimensionlab-website.sh
+++ /dev/null
@@ -1,362 +0,0 @@
-#!/usr/bin/env bash
-set -Eeuo pipefail
-
-APP_NAME="${APP_NAME:-dimensionlab-website}"
-SERVICE_NAME="${SERVICE_NAME:-dimensionlab-website.service}"
-CONTAINER_NAME="${CONTAINER_NAME:-dimensionlab-website}"
-IMAGE_REPO="${IMAGE_REPO:-localhost/dimensionlab-website}"
-CONTAINERFILE="${CONTAINERFILE:-apps/web/Containerfile}"
-PUBLIC_URL="${PUBLIC_URL:-https://dimensionlab.net/}"
-TILE_BATCH_URL="${TILE_BATCH_URL:-https://dimensionlab.net/api/dashboard/tiles}"
-DEPLOY_RESTART_STRATEGY="${DEPLOY_RESTART_STRATEGY:-auto}"
-DEPLOY_SMOKE_TIMEOUT_SECONDS="${DEPLOY_SMOKE_TIMEOUT_SECONDS:-120}"
-DEPLOY_CONTAINER_START_TIMEOUT_SECONDS="${DEPLOY_CONTAINER_START_TIMEOUT_SECONDS:-90}"
-
-dry_run=false
-rollback_tag=""
-release_tag=""
-latest_tag="${IMAGE_REPO}:latest"
-container_cli=""
-deployment_started=false
-rollback_done=false
-rollback_in_progress=false
-
-usage() {
- cat <&2
- if [ "${deployment_started:-false}" = "true" ] && [ "${rollback_in_progress:-false}" != "true" ]; then
- rollback || true
- fi
- exit 1
-}
-
-run() {
- if "$dry_run"; then
- printf '[deploy:%s] DRY-RUN:' "$APP_NAME"
- printf ' %q' "$@"
- printf '\n'
- return 0
- fi
-
- "$@"
-}
-
-for arg in "$@"; do
- case "$arg" in
- --dry-run)
- dry_run=true
- ;;
- -h|--help)
- usage
- exit 0
- ;;
- *)
- usage >&2
- fail "unknown argument: $arg"
- ;;
- esac
-done
-
-deployment_ref() {
- printf '%s' "${DEPLOY_REF:-${GITHUB_REF:-${FORGEJO_REF:-}}}"
-}
-
-deployment_event() {
- printf '%s' "${DEPLOY_EVENT_NAME:-${GITHUB_EVENT_NAME:-${FORGEJO_EVENT_NAME:-}}}"
-}
-
-require_main_push() {
- local event
- local ref
-
- event="$(deployment_event)"
- ref="$(deployment_ref)"
-
- if [ -n "$event" ] && [ "$event" != "push" ]; then
- fail "refusing to deploy for event '$event'; production deploys only run for push"
- fi
-
- if [ -n "$ref" ]; then
- [ "$ref" = "refs/heads/main" ] || fail "refusing to deploy ref '$ref'; expected refs/heads/main"
- return 0
- fi
-
- local branch
- branch="$(git branch --show-current 2>/dev/null || true)"
- [ "$branch" = "main" ] || fail "refusing to deploy branch '$branch'; expected main"
-}
-
-select_container_cli() {
- if [ -n "${DEPLOY_CONTAINER_CLI:-}" ]; then
- command -v "$DEPLOY_CONTAINER_CLI" >/dev/null 2>&1 || fail "container CLI not found: $DEPLOY_CONTAINER_CLI"
- container_cli="$DEPLOY_CONTAINER_CLI"
- return 0
- fi
-
- if command -v podman >/dev/null 2>&1; then
- container_cli="podman"
- return 0
- fi
-
- if command -v docker >/dev/null 2>&1; then
- container_cli="docker"
- return 0
- fi
-
- fail "podman or docker is required"
-}
-
-current_sha() {
- if [ -n "${DEPLOY_SHA:-${GITHUB_SHA:-}}" ]; then
- printf '%s' "${DEPLOY_SHA:-${GITHUB_SHA:-}}"
- return 0
- fi
-
- git rev-parse HEAD
-}
-
-tag_existing_latest_for_rollback() {
- rollback_tag="${IMAGE_REPO}:rollback-$(date -u +%Y%m%d%H%M%S)"
- if "$container_cli" image inspect "$latest_tag" >/dev/null 2>&1; then
- log "tagging current latest image as $rollback_tag"
- run "$container_cli" tag "$latest_tag" "$rollback_tag"
- else
- log "no existing $latest_tag image found; rollback image tag will not be created"
- rollback_tag=""
- fi
-}
-
-container_systemd_unit() {
- "$container_cli" inspect "$CONTAINER_NAME" \
- --format '{{ index .Config.Labels "PODMAN_SYSTEMD_UNIT" }}' 2>/dev/null || true
-}
-
-require_container_managed_by_service() {
- local unit
-
- if "$dry_run"; then
- log "DRY-RUN: would require $CONTAINER_NAME to be managed by $SERVICE_NAME"
- return 0
- fi
-
- unit="$(container_systemd_unit)"
- [ "$unit" = "$SERVICE_NAME" ] || fail "refusing to stop $CONTAINER_NAME; expected PODMAN_SYSTEMD_UNIT=$SERVICE_NAME, got '${unit:-unset}'"
-}
-
-validate_restart_strategy() {
- case "$DEPLOY_RESTART_STRATEGY" in
- systemctl)
- if ! "$dry_run" && ! systemctl --user show "$SERVICE_NAME" >/dev/null 2>&1; then
- fail "systemctl --user cannot access $SERVICE_NAME"
- fi
- ;;
- quadlet-container|kill-container)
- require_container_managed_by_service
- ;;
- auto)
- if "$dry_run"; then
- log "DRY-RUN: would validate automatic restart strategy"
- elif ! command -v systemctl >/dev/null 2>&1 || ! systemctl --user show "$SERVICE_NAME" >/dev/null 2>&1; then
- require_container_managed_by_service
- fi
- ;;
- *)
- fail "unknown DEPLOY_RESTART_STRATEGY: $DEPLOY_RESTART_STRATEGY"
- ;;
- esac
-}
-
-initialize_submodules() {
- log "initializing submodules"
- run git config --global url."https://git.dimensionlab.net/".insteadOf "ssh://git@git.dimensionlab.net/"
- run git submodule update --init --recursive
-}
-
-build_image() {
- local sha
- local short_sha
-
- sha="$(current_sha)"
- short_sha="${sha:0:12}"
- release_tag="${IMAGE_REPO}:${short_sha}"
-
- [ -f "$CONTAINERFILE" ] || fail "containerfile not found: $CONTAINERFILE"
-
- log "building $release_tag and $latest_tag from $CONTAINERFILE"
- run "$container_cli" build -f "$CONTAINERFILE" -t "$release_tag" -t "$latest_tag" .
-}
-
-restart_service() {
- log "restarting $SERVICE_NAME with strategy $DEPLOY_RESTART_STRATEGY"
-
- case "$DEPLOY_RESTART_STRATEGY" in
- systemctl)
- run systemctl --user restart "$SERVICE_NAME"
- ;;
- quadlet-container|kill-container)
- require_container_managed_by_service
- run "$container_cli" stop "$CONTAINER_NAME"
- ;;
- auto)
- if command -v systemctl >/dev/null 2>&1 && systemctl --user is-active "$SERVICE_NAME" >/dev/null 2>&1; then
- run systemctl --user restart "$SERVICE_NAME"
- else
- require_container_managed_by_service
- run "$container_cli" stop "$CONTAINER_NAME"
- fi
- ;;
- *)
- fail "unknown DEPLOY_RESTART_STRATEGY: $DEPLOY_RESTART_STRATEGY"
- ;;
- esac
-}
-
-latest_image_id() {
- "$container_cli" image inspect "$latest_tag" --format '{{.Id}}' 2>/dev/null || true
-}
-
-container_image_id() {
- "$container_cli" inspect "$CONTAINER_NAME" --format '{{.Image}}' 2>/dev/null || true
-}
-
-container_running() {
- local running
- running="$("$container_cli" inspect "$CONTAINER_NAME" --format '{{.State.Running}}' 2>/dev/null || true)"
- [ "$running" = "true" ]
-}
-
-wait_for_container_restart() {
- local expected_image
-
- if "$dry_run"; then
- log "DRY-RUN: would wait for $CONTAINER_NAME to run $latest_tag"
- return 0
- fi
-
- expected_image="$(latest_image_id)"
- [ -n "$expected_image" ] || fail "could not resolve image id for $latest_tag"
-
- wait_for_container_image "$expected_image" "new image" || fail "$CONTAINER_NAME did not restart on $latest_tag within ${DEPLOY_CONTAINER_START_TIMEOUT_SECONDS}s"
-}
-
-wait_for_container_image() {
- local expected_image="$1"
- local label="$2"
- local deadline
-
- deadline=$((SECONDS + DEPLOY_CONTAINER_START_TIMEOUT_SECONDS))
-
- while [ "$SECONDS" -lt "$deadline" ]; do
- if container_running && [ "$(container_image_id)" = "$expected_image" ]; then
- log "$CONTAINER_NAME is running the $label"
- return 0
- fi
-
- sleep 2
- done
-
- return 1
-}
-
-smoke_get() {
- local url="$1"
- curl -fsS --max-time 10 -o /dev/null "$url"
-}
-
-smoke_tiles() {
- local response
-
- response="$(
- curl -fsS --max-time 20 \
- -H "content-type: application/json" \
- --data '{"tiles":[{"kind":"status","stripId":"footer-status","id":"system-status"}]}' \
- "$TILE_BATCH_URL"
- )"
-
- [[ "$response" == *'"state":"ready"'* ]]
-}
-
-wait_for_smoke() {
- local deadline
-
- if "$dry_run"; then
- log "DRY-RUN: would smoke check $PUBLIC_URL and $TILE_BATCH_URL"
- return 0
- fi
-
- deadline=$((SECONDS + DEPLOY_SMOKE_TIMEOUT_SECONDS))
- until smoke_get "$PUBLIC_URL" && smoke_tiles; do
- if [ "$SECONDS" -ge "$deadline" ]; then
- fail "smoke checks failed for $PUBLIC_URL and $TILE_BATCH_URL"
- fi
-
- sleep 3
- done
-
- log "smoke checks passed"
-}
-
-rollback() {
- local rollback_image
-
- if [ "$deployment_started" != "true" ] || [ -z "$rollback_tag" ] || [ "$rollback_done" = "true" ]; then
- return 0
- fi
-
- rollback_done=true
- rollback_in_progress=true
- printf '[deploy:%s] rolling back to %s\n' "$APP_NAME" "$rollback_tag" >&2
- rollback_image="$("$container_cli" image inspect "$rollback_tag" --format '{{.Id}}' 2>/dev/null || true)"
- "$container_cli" tag "$rollback_tag" "$latest_tag" || true
- if container_running; then
- restart_service || true
- else
- printf '[deploy:%s] waiting for %s to recover with rollback image\n' "$APP_NAME" "$SERVICE_NAME" >&2
- fi
- if [ -n "$rollback_image" ] && wait_for_container_image "$rollback_image" "rollback image"; then
- printf '[deploy:%s] rollback image is running\n' "$APP_NAME" >&2
- else
- printf '[deploy:%s] ERROR: rollback image did not become healthy\n' "$APP_NAME" >&2
- fi
- rollback_in_progress=false
-}
-
-on_error() {
- local status=$?
- rollback
- exit "$status"
-}
-
-trap on_error ERR
-
-require_main_push
-select_container_cli
-validate_restart_strategy
-
-log "using container CLI: $container_cli"
-log "target image: $latest_tag"
-log "target service: $SERVICE_NAME"
-
-initialize_submodules
-tag_existing_latest_for_rollback
-build_image
-deployment_started=true
-restart_service
-wait_for_container_restart
-wait_for_smoke
-
-log "deployment finished"
diff --git a/src/app.css b/src/app.css
new file mode 100644
index 0000000..4fa34dd
--- /dev/null
+++ b/src/app.css
@@ -0,0 +1 @@
+@import "./lib/ui/tokens.css";
diff --git a/src/app.html b/src/app.html
new file mode 100644
index 0000000..adf8bd8
--- /dev/null
+++ b/src/app.html
@@ -0,0 +1,11 @@
+
+
+
+
+
+ %sveltekit.head%
+
+
+ %sveltekit.body%
+
+
diff --git a/apps/web/src/lib/dashboard-seed/dimensionlab.test.ts b/src/lib/model/fixtures/dimensionlab.test.ts
similarity index 98%
rename from apps/web/src/lib/dashboard-seed/dimensionlab.test.ts
rename to src/lib/model/fixtures/dimensionlab.test.ts
index 1a97c3b..ae13cbf 100644
--- a/apps/web/src/lib/dashboard-seed/dimensionlab.test.ts
+++ b/src/lib/model/fixtures/dimensionlab.test.ts
@@ -7,7 +7,7 @@ import type {
DatasourceReference,
ServiceEntry,
TelemetryCard,
-} from "@dimensionlab/dashboard-model";
+} from "../schema";
describe("Dimension Lab dashboard seed", () => {
test("defines the primary first-screen sections as model data", () => {
@@ -131,7 +131,6 @@ const verifiedSeedIconIds = new Set([
"mdi:thermometer",
"mdi:web",
"mdi:weather-sunny",
- "simple-icons:adguard",
"simple-icons:adminer",
"simple-icons:amazonwebservices",
"simple-icons:cockpit",
diff --git a/apps/web/src/lib/dashboard-seed/dimensionlab.ts b/src/lib/model/fixtures/dimensionlab.ts
similarity index 71%
rename from apps/web/src/lib/dashboard-seed/dimensionlab.ts
rename to src/lib/model/fixtures/dimensionlab.ts
index 59162e6..9890fef 100644
--- a/apps/web/src/lib/dashboard-seed/dimensionlab.ts
+++ b/src/lib/model/fixtures/dimensionlab.ts
@@ -7,7 +7,7 @@ import {
type ServiceGroup,
type Severity,
type TelemetryCard,
-} from "@dimensionlab/dashboard-model";
+} from "../schema";
type NumericValueKind = "percent" | "bytes" | "temperature" | "latency" | "number";
@@ -36,35 +36,11 @@ interface ServiceSeed {
detail?: string;
}
-const REAL_FILESYSTEM_FILTER =
- 'fstype!~"tmpfs|overlay|squashfs|nsfs|tracefs|autofs|proc|sysfs|cgroup2|devtmpfs|securityfs|debugfs|pstore|bpf|configfs|selinuxfs|mqueue|hugetlbfs|fusectl|ramfs"';
-const USER_MOUNT_FILTER =
- 'mountpoint=~"^/home($|/)|^/srv($|/)|^/data($|/)|^/mnt($|/)|^/media($|/)",mountpoint!~"^/(boot|boot/efi|efi|var|usr|opt|run|dev|proc|sys)($|/)"';
-const SYSTEM_MOUNT_FILTER =
- 'mountpoint=~"^/$|^/boot($|/)|^/boot/efi$|^/var($|/)|^/usr($|/)|^/opt($|/)",mountpoint!~"^/home($|/)|^/srv($|/)|^/data($|/)|^/mnt($|/)|^/media($|/)"';
-
-const diskUsedQuery = (mountFilter: string) => {
- const selector = `job="node",${REAL_FILESYSTEM_FILTER},${mountFilter}`;
- return `topk(1, max by (host, mountpoint) (100 * (1 - node_filesystem_avail_bytes{${selector}} / node_filesystem_size_bytes{${selector}})))`;
-};
-const ramQuery = (host: string) =>
- `100 * (1 - node_memory_MemAvailable_bytes{job="node",host="${host}"} / node_memory_MemTotal_bytes{job="node",host="${host}"})`;
-const gpuQuery = (name: string, metric: string) =>
- `${metric}{job="node",name=~".*${name}.*"}`;
-const gpuVramQuery = (name: string) =>
- `100 * nvidia_gpu_memory_used_bytes{job="node",name=~".*${name}.*"} / nvidia_gpu_memory_total_bytes{job="node",name=~".*${name}.*"}`;
-const hostCpuQuery =
- 'topk(1, 100 * (1 - avg by (host) (rate(node_cpu_seconds_total{job="node",mode="idle"}[5m]))))';
-const topCpuQuery =
- 'topk(1, 100 * rate(podman_container_cpu_seconds_total{job=~"podman-.*"}[5m]) * on(host,id) group_left(name) podman_container_info{job=~"podman-.*"})';
-const topRamQuery =
- 'topk(1, podman_container_mem_usage_bytes{job=~"podman-.*"} * on(host,id) group_left(name) podman_container_info{job=~"podman-.*"})';
-
export const dimensionLabDashboardFixture: DashboardDocument = {
schemaVersion: DASHBOARD_SCHEMA_VERSION,
metadata: {
title: "System Overview",
- subtitle: "Capacity, noise & top consumers",
+ subtitle: "Dimension Lab home infra, GPU, and automation surface",
description:
"Initial Dimension Lab dashboard seed data. Runtime values are fallback values until datasource adapters are enabled.",
timezone: "Europe/Amsterdam",
@@ -105,7 +81,7 @@ export const dimensionLabDashboardFixture: DashboardDocument = {
severity: "ok",
thresholds: percentThresholds(),
datasource: prometheus(
- ramQuery("linux-infra"),
+ '100 * (1 - node_memory_MemAvailable_bytes{instance="linux-infra"} / node_memory_MemTotal_bytes{instance="linux-infra"})',
),
sparkline: [18, 18, 19, 19, 18, 19],
}),
@@ -119,7 +95,7 @@ export const dimensionLabDashboardFixture: DashboardDocument = {
severity: "ok",
thresholds: percentThresholds(),
datasource: prometheus(
- ramQuery("linux"),
+ '100 * (1 - node_memory_MemAvailable_bytes{instance="linux-gpu"} / node_memory_MemTotal_bytes{instance="linux-gpu"})',
),
sparkline: [16, 16, 17, 17, 17, 17],
}),
@@ -133,7 +109,7 @@ export const dimensionLabDashboardFixture: DashboardDocument = {
severity: "ok",
thresholds: percentThresholds(),
datasource: prometheus(
- ramQuery("network-core"),
+ '100 * (1 - node_memory_MemAvailable_bytes{instance="network-core"} / node_memory_MemTotal_bytes{instance="network-core"})',
),
sparkline: [5, 5, 5, 6, 5, 5],
}),
@@ -147,7 +123,7 @@ export const dimensionLabDashboardFixture: DashboardDocument = {
severity: "ok",
thresholds: percentThresholds(80, 92),
datasource: prometheus(
- diskUsedQuery(USER_MOUNT_FILTER),
+ '100 * (1 - node_filesystem_avail_bytes{mountpoint="/home"} / node_filesystem_size_bytes{mountpoint="/home"})',
),
sparkline: [27, 27, 28, 29, 29, 29],
}),
@@ -161,7 +137,7 @@ export const dimensionLabDashboardFixture: DashboardDocument = {
severity: "ok",
thresholds: percentThresholds(80, 92),
datasource: prometheus(
- diskUsedQuery(SYSTEM_MOUNT_FILTER),
+ '100 * (1 - node_filesystem_avail_bytes{mountpoint="/boot"} / node_filesystem_size_bytes{mountpoint="/boot"})',
),
sparkline: [48, 48, 49, 49, 49, 49],
}),
@@ -175,7 +151,7 @@ export const dimensionLabDashboardFixture: DashboardDocument = {
severity: "ok",
thresholds: percentThresholds(75, 90),
datasource: prometheus(
- hostCpuQuery,
+ '100 - (avg by(instance) (rate(node_cpu_seconds_total{instance="linux-infra",mode="idle"}[5m])) * 100)',
),
sparkline: [2, 3, 3, 4, 3, 3],
}),
@@ -189,7 +165,9 @@ export const dimensionLabDashboardFixture: DashboardDocument = {
detail: "fallback - top container CPU pending label mapping",
severity: "ok",
thresholds: percentThresholds(70, 90),
- datasource: prometheus(topCpuQuery),
+ datasource: placeholder(
+ "fallback top-container CPU query pending container label normalization",
+ ),
sparkline: [6.1, 6.4, 7.0, 7.5, 7.2, 7.9],
}),
metric({
@@ -202,7 +180,9 @@ export const dimensionLabDashboardFixture: DashboardDocument = {
detail: "fallback - top container RAM pending label mapping",
severity: "danger",
thresholds: { warning: 3 * 1024 ** 3, danger: 5 * 1024 ** 3 },
- datasource: prometheus(topRamQuery),
+ datasource: placeholder(
+ "fallback top-container memory query pending container label normalization",
+ ),
sparkline: [
3.1 * 1024 ** 3,
3.5 * 1024 ** 3,
@@ -221,7 +201,7 @@ export const dimensionLabDashboardFixture: DashboardDocument = {
detail: "fallback - RTX 3060 GPU utilization",
severity: "ok",
thresholds: percentThresholds(85, 95),
- datasource: prometheus(gpuQuery("3060", "nvidia_gpu_utilization_percent")),
+ datasource: prometheus('DCGM_FI_DEV_GPU_UTIL{gpu="rtx-3060"}'),
sparkline: [0, 0, 0, 0, 0, 0],
}),
metric({
@@ -234,7 +214,7 @@ export const dimensionLabDashboardFixture: DashboardDocument = {
severity: "ok",
thresholds: percentThresholds(80, 92),
datasource: prometheus(
- gpuVramQuery("3060"),
+ '100 * DCGM_FI_DEV_FB_USED{gpu="rtx-3060"} / (DCGM_FI_DEV_FB_USED{gpu="rtx-3060"} + DCGM_FI_DEV_FB_FREE{gpu="rtx-3060"})',
),
sparkline: [0, 0, 0, 0, 0, 0],
}),
@@ -247,7 +227,7 @@ export const dimensionLabDashboardFixture: DashboardDocument = {
detail: "fallback - RTX 3060 temperature",
severity: "ok",
thresholds: { warning: 75, danger: 85 },
- datasource: prometheus(gpuQuery("3060", "nvidia_gpu_temperature_celsius")),
+ datasource: prometheus('DCGM_FI_DEV_GPU_TEMP{gpu="rtx-3060"}'),
sparkline: [52, 53, 54, 54, 53, 54],
}),
metric({
@@ -259,7 +239,7 @@ export const dimensionLabDashboardFixture: DashboardDocument = {
detail: "fallback - RTX 3060 fan duty",
severity: "ok",
thresholds: percentThresholds(80, 95),
- datasource: prometheus(gpuQuery("3060", "nvidia_gpu_fan_speed_percent")),
+ datasource: prometheus('DCGM_FI_DEV_FAN_SPEED{gpu="rtx-3060"}'),
sparkline: [0, 0, 0, 0, 0, 0],
}),
metric({
@@ -271,7 +251,7 @@ export const dimensionLabDashboardFixture: DashboardDocument = {
detail: "fallback - RTX 3090 GPU utilization",
severity: "ok",
thresholds: percentThresholds(85, 95),
- datasource: prometheus(gpuQuery("3090", "nvidia_gpu_utilization_percent")),
+ datasource: prometheus('DCGM_FI_DEV_GPU_UTIL{gpu="rtx-3090"}'),
sparkline: [0, 0, 0, 0, 0, 0],
}),
metric({
@@ -284,7 +264,7 @@ export const dimensionLabDashboardFixture: DashboardDocument = {
severity: "warning",
thresholds: percentThresholds(70, 90),
datasource: prometheus(
- gpuVramQuery("3090"),
+ '100 * DCGM_FI_DEV_FB_USED{gpu="rtx-3090"} / (DCGM_FI_DEV_FB_USED{gpu="rtx-3090"} + DCGM_FI_DEV_FB_FREE{gpu="rtx-3090"})',
),
sparkline: [68, 70, 72, 74, 73, 74],
}),
@@ -297,7 +277,7 @@ export const dimensionLabDashboardFixture: DashboardDocument = {
detail: "fallback - RTX 3090 temperature",
severity: "ok",
thresholds: { warning: 75, danger: 85 },
- datasource: prometheus(gpuQuery("3090", "nvidia_gpu_temperature_celsius")),
+ datasource: prometheus('DCGM_FI_DEV_GPU_TEMP{gpu="rtx-3090"}'),
sparkline: [49, 50, 50, 51, 50, 50],
}),
metric({
@@ -309,7 +289,7 @@ export const dimensionLabDashboardFixture: DashboardDocument = {
detail: "fallback - RTX 3090 fan duty",
severity: "ok",
thresholds: percentThresholds(80, 95),
- datasource: prometheus(gpuQuery("3090", "nvidia_gpu_fan_speed_percent")),
+ datasource: prometheus('DCGM_FI_DEV_FAN_SPEED{gpu="rtx-3090"}'),
sparkline: [0, 0, 0, 0, 0, 0],
}),
],
@@ -321,7 +301,7 @@ export const dimensionLabDashboardFixture: DashboardDocument = {
description: "Password manager",
icon: "simple-icons:vaultwarden",
href: "https://vault.dimensionlab.net",
- datasource: uptimeMonitor(1),
+ datasource: httpStatus("https://vault.dimensionlab.net/alive"),
}),
service({
id: "forgejo",
@@ -329,7 +309,7 @@ export const dimensionLabDashboardFixture: DashboardDocument = {
description: "Git repositories",
icon: "simple-icons:forgejo",
href: "https://git.dimensionlab.net",
- datasource: uptimeMonitor(2),
+ datasource: httpStatus("https://git.dimensionlab.net/api/healthz"),
}),
service({
id: "wiki",
@@ -337,7 +317,7 @@ export const dimensionLabDashboardFixture: DashboardDocument = {
description: "Internal documentation",
icon: "simple-icons:wikidotjs",
href: "https://wiki.dimensionlab.net",
- datasource: uptimeMonitor(3),
+ datasource: httpStatus("https://wiki.dimensionlab.net"),
}),
service({
id: "aws-start",
@@ -345,23 +325,7 @@ export const dimensionLabDashboardFixture: DashboardDocument = {
description: "AWS access portal",
icon: "simple-icons:amazonwebservices",
href: "https://dimensionlab.awsapps.com/start",
- datasource: uptimeMonitor(21),
- }),
- service({
- id: "adguard-primary",
- label: "AdGuard Primary",
- description: "DNS filtering and DHCP",
- icon: "simple-icons:adguard",
- href: "https://control.dimensionlab.net",
- datasource: uptimeMonitor(14),
- }),
- service({
- id: "adguard-secondary",
- label: "AdGuard Secondary",
- description: "Fallback DNS",
- icon: "simple-icons:adguard",
- href: "https://control-secondary.dimensionlab.net",
- datasource: uptimeMonitor(18),
+ datasource: httpStatus("https://dimensionlab.awsapps.com/start"),
}),
]),
group("monitoring", "Monitoring", [
@@ -371,7 +335,7 @@ export const dimensionLabDashboardFixture: DashboardDocument = {
description: "Capacity and noise dashboard",
icon: "simple-icons:grafana",
href: "https://grafana.dimensionlab.net",
- datasource: uptimeMonitor(11),
+ datasource: httpStatus("https://grafana.dimensionlab.net/api/health"),
}),
service({
id: "uptime-kuma",
@@ -379,7 +343,7 @@ export const dimensionLabDashboardFixture: DashboardDocument = {
description: "Service uptime checks",
icon: "simple-icons:uptimekuma",
href: "https://uptime.dimensionlab.net",
- datasource: uptimeMonitor(10),
+ datasource: httpStatus("https://uptime.dimensionlab.net/status/dimensionlab"),
}),
service({
id: "prometheus",
@@ -387,7 +351,7 @@ export const dimensionLabDashboardFixture: DashboardDocument = {
description: "Metrics database",
icon: "simple-icons:prometheus",
href: "https://prometheus.dimensionlab.net",
- datasource: uptimeMonitor(12),
+ datasource: httpStatus("https://prometheus.dimensionlab.net/-/ready"),
}),
service({
id: "backrest",
@@ -395,7 +359,7 @@ export const dimensionLabDashboardFixture: DashboardDocument = {
description: "Restic backup manager",
icon: "mdi:backup-restore",
href: "https://backups.dimensionlab.net",
- datasource: uptimeMonitor(13),
+ datasource: httpStatus("https://backups.dimensionlab.net"),
}),
]),
group("ai-automation", "AI & Automation", [
@@ -405,7 +369,7 @@ export const dimensionLabDashboardFixture: DashboardDocument = {
description: "Workflow automation",
icon: "simple-icons:n8n",
href: "https://workflows.dimensionlab.net",
- datasource: uptimeMonitor(4),
+ datasource: httpStatus("https://workflows.dimensionlab.net/healthz"),
}),
service({
id: "open-webui",
@@ -413,7 +377,7 @@ export const dimensionLabDashboardFixture: DashboardDocument = {
description: "Chat and model interface",
icon: "mdi:web",
href: "https://webui.dimensionlab.net",
- datasource: uptimeMonitor(5),
+ datasource: httpStatus("https://webui.dimensionlab.net/health"),
}),
service({
id: "comfyui",
@@ -421,7 +385,7 @@ export const dimensionLabDashboardFixture: DashboardDocument = {
description: "Image generation workflows",
icon: "mdi:image-edit-outline",
href: "https://comfy.dimensionlab.net",
- datasource: uptimeMonitor(6),
+ datasource: httpStatus("https://comfy.dimensionlab.net"),
}),
service({
id: "models",
@@ -429,7 +393,7 @@ export const dimensionLabDashboardFixture: DashboardDocument = {
description: "Local model management",
icon: "mdi:brain",
href: "https://models.dimensionlab.net",
- datasource: uptimeMonitor(7),
+ datasource: httpStatus("https://models.dimensionlab.net/api/tags"),
}),
]),
group("systems", "Systems", [
@@ -439,7 +403,7 @@ export const dimensionLabDashboardFixture: DashboardDocument = {
description: "PostgreSQL database browser",
icon: "simple-icons:adminer",
href: "https://db.dimensionlab.net",
- datasource: uptimeMonitor(17),
+ datasource: httpStatus("https://db.dimensionlab.net"),
}),
service({
id: "assistant",
@@ -447,7 +411,7 @@ export const dimensionLabDashboardFixture: DashboardDocument = {
description: "Personal AI agent gateway",
icon: "mdi:robot-outline",
href: "https://assistant.dimensionlab.net",
- datasource: uptimeMonitor(8),
+ datasource: httpStatus("https://assistant.dimensionlab.net/health"),
}),
service({
id: "suna",
@@ -455,7 +419,7 @@ export const dimensionLabDashboardFixture: DashboardDocument = {
description: "AI command center",
icon: "mdi:account-hard-hat-outline",
href: "https://suna.dimensionlab.net",
- datasource: uptimeMonitor(9),
+ datasource: httpStatus("https://suna.dimensionlab.net"),
}),
service({
id: "cockpit-infra",
@@ -463,23 +427,7 @@ export const dimensionLabDashboardFixture: DashboardDocument = {
description: "linux-infra server console",
icon: "simple-icons:cockpit",
href: "https://infra-cockpit.dimensionlab.net",
- datasource: uptimeMonitor(15),
- }),
- service({
- id: "cockpit-gpu",
- label: "Cockpit GPU",
- description: "Linux GPU server console",
- icon: "simple-icons:cockpit",
- href: "https://linux-cockpit.dimensionlab.net",
- datasource: uptimeMonitor(16),
- }),
- service({
- id: "cockpit-network-core",
- label: "Cockpit Network Core",
- description: "i3 NUC DNS/DHCP console",
- icon: "simple-icons:cockpit",
- href: "https://network-core.dimensionlab.net",
- datasource: uptimeMonitor(19),
+ datasource: httpStatus("https://infra-cockpit.dimensionlab.net"),
}),
]),
group("runtime-health", "Runtime Health", [
@@ -489,7 +437,7 @@ export const dimensionLabDashboardFixture: DashboardDocument = {
description: "Public Git SSH relay",
icon: "simple-icons:forgejo",
href: "https://uptime.dimensionlab.net/status/dimensionlab",
- datasource: uptimeMonitor(28),
+ datasource: custom("tcp:git.dimensionlab.net:22"),
detail: "fallback - uptime monitor pending",
}),
service({
@@ -497,7 +445,7 @@ export const dimensionLabDashboardFixture: DashboardDocument = {
label: "PostgreSQL",
description: "Shared application database",
icon: "simple-icons:postgresql",
- datasource: uptimeMonitor(22),
+ datasource: prometheus('pg_up{cluster="dimensionlab"}'),
detail: "fallback - postgres exporter pending",
}),
service({
@@ -505,7 +453,7 @@ export const dimensionLabDashboardFixture: DashboardDocument = {
label: "Ollama API",
description: "Local model API",
icon: "simple-icons:ollama",
- datasource: uptimeMonitor(23),
+ datasource: httpStatus("http://linux-gpu:11434/api/tags"),
detail: "fallback - internal health check pending",
}),
service({
@@ -513,34 +461,10 @@ export const dimensionLabDashboardFixture: DashboardDocument = {
label: "Node Exporter",
description: "Host metrics exporter",
icon: "simple-icons:prometheus",
- datasource: uptimeMonitor(24),
- detail: "fallback - exporter health from Uptime Kuma",
- }),
- service({
- id: "podman-user-exporter",
- label: "Podman User Exporter",
- description: "Rootless container metrics",
- icon: "simple-icons:prometheus",
- datasource: uptimeMonitor(25),
- detail: "fallback - exporter health from Uptime Kuma",
- }),
- service({
- id: "podman-system-exporter",
- label: "Podman System Exporter",
- description: "System container metrics",
- icon: "simple-icons:prometheus",
- datasource: uptimeMonitor(26),
- detail: "fallback - exporter health from Uptime Kuma",
- }),
- service({
- id: "network-core-node-exporter",
- label: "Network Core Node Exporter",
- description: "i3 DNS/DHCP metrics",
- icon: "simple-icons:prometheus",
- datasource: uptimeMonitor(27),
+ datasource: prometheus('up{job="node-exporter"}'),
detail: "fallback - exporter health from Prometheus",
}),
- ], "grid"),
+ ]),
],
statusStrips: [
{
@@ -550,7 +474,6 @@ export const dimensionLabDashboardFixture: DashboardDocument = {
{ id: "last-sync", label: "Last Sync", value: "fallback: 2 minutes ago", severity: "stale" },
{ id: "uptime", label: "Uptime", value: "fallback: 28d 14h 32m", severity: "ok" },
{ id: "load-avg", label: "Load Avg", value: "fallback: 0.47 0.53 0.59", severity: "neutral" },
- { id: "auto-refresh", label: "Auto Refresh", value: "fallback: 15s", severity: "neutral" },
],
},
],
@@ -627,17 +550,12 @@ function service(seed: ServiceSeed): ServiceEntry {
};
}
-function group(
- id: string,
- title: string,
- services: ServiceEntry[],
- layout: ServiceGroup["layout"] = "list",
-): ServiceGroup {
+function group(id: string, title: string, services: ServiceEntry[]): ServiceGroup {
return {
id,
- layout,
- services,
title,
+ layout: "list",
+ services,
};
}
@@ -661,13 +579,17 @@ function httpStatus(url: string): DatasourceReference {
};
}
+function custom(reference: string): DatasourceReference {
+ return {
+ type: "external",
+ adapter: "custom",
+ reference,
+ };
+}
+
function placeholder(reason: string): DatasourceReference {
return {
type: "placeholder",
reason,
};
}
-
-function uptimeMonitor(id: number): DatasourceReference {
- return httpStatus(`https://uptime.dimensionlab.net/_homepage-badge/${id}`);
-}
diff --git a/packages/dashboard-model/src/fixtures/generic.ts b/src/lib/model/fixtures/generic.ts
similarity index 100%
rename from packages/dashboard-model/src/fixtures/generic.ts
rename to src/lib/model/fixtures/generic.ts
diff --git a/src/lib/model/fixtures/index.ts b/src/lib/model/fixtures/index.ts
new file mode 100644
index 0000000..a04278d
--- /dev/null
+++ b/src/lib/model/fixtures/index.ts
@@ -0,0 +1,2 @@
+export { dimensionLabDashboardFixture } from "./dimensionlab";
+export { genericDashboardFixture } from "./generic";
diff --git a/packages/dashboard-model/src/index.ts b/src/lib/model/index.ts
similarity index 83%
rename from packages/dashboard-model/src/index.ts
rename to src/lib/model/index.ts
index 5ae349a..1a028cc 100644
--- a/packages/dashboard-model/src/index.ts
+++ b/src/lib/model/index.ts
@@ -1,11 +1,6 @@
export {
DASHBOARD_SCHEMA_VERSION,
DashboardDocumentSchema,
- DatasourceReferenceSchema,
- ServiceEntrySchema,
- ServiceGroupSchema,
- TelemetryCardSchema,
- ThresholdSchema,
dashboardDocumentJsonSchema,
type DashboardDocument,
type DashboardModule,
diff --git a/packages/dashboard-model/src/schema.test.ts b/src/lib/model/schema.test.ts
similarity index 96%
rename from packages/dashboard-model/src/schema.test.ts
rename to src/lib/model/schema.test.ts
index 9975f87..7eeed74 100644
--- a/packages/dashboard-model/src/schema.test.ts
+++ b/src/lib/model/schema.test.ts
@@ -5,6 +5,7 @@ import {
validateDashboardDocument,
} from ".";
import {
+ dimensionLabDashboardFixture,
genericDashboardFixture,
} from "./fixtures";
@@ -18,6 +19,12 @@ 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", () => {
const invalid = {
...genericDashboardFixture,
diff --git a/packages/dashboard-model/src/schema.ts b/src/lib/model/schema.ts
similarity index 95%
rename from packages/dashboard-model/src/schema.ts
rename to src/lib/model/schema.ts
index 86e3ab7..e0215a4 100644
--- a/packages/dashboard-model/src/schema.ts
+++ b/src/lib/model/schema.ts
@@ -27,7 +27,7 @@ const LinkSchema = Type.Object(
{ additionalProperties: false },
);
-export const StaticDatasourceSchema = Type.Object(
+const StaticDatasourceSchema = Type.Object(
{
type: Type.Literal("static"),
label: Type.Optional(Type.String({ minLength: 1 })),
@@ -36,7 +36,7 @@ export const StaticDatasourceSchema = Type.Object(
{ additionalProperties: false },
);
-export const PlaceholderDatasourceSchema = Type.Object(
+const PlaceholderDatasourceSchema = Type.Object(
{
type: Type.Literal("placeholder"),
reason: Type.String({ minLength: 1 }),
@@ -44,7 +44,7 @@ export const PlaceholderDatasourceSchema = Type.Object(
{ additionalProperties: false },
);
-export const ExternalDatasourceSchema = Type.Object(
+const ExternalDatasourceSchema = Type.Object(
{
type: Type.Literal("external"),
adapter: Type.Union([
@@ -58,7 +58,7 @@ export const ExternalDatasourceSchema = Type.Object(
{ additionalProperties: false },
);
-export const DatasourceReferenceSchema = Type.Union([
+const DatasourceReferenceSchema = Type.Union([
StaticDatasourceSchema,
PlaceholderDatasourceSchema,
ExternalDatasourceSchema,
@@ -98,13 +98,13 @@ const TextMetricValueSchema = Type.Object(
{ additionalProperties: false },
);
-export const MetricValueSchema = Type.Union([
+const MetricValueSchema = Type.Union([
PercentMetricValueSchema,
NonNegativeMetricValueSchema,
TextMetricValueSchema,
]);
-export const ThresholdSchema = Type.Object(
+const ThresholdSchema = Type.Object(
{
warning: Type.Optional(Type.Number({ minimum: 0 })),
danger: Type.Optional(Type.Number({ minimum: 0 })),
diff --git a/packages/dashboard-model/src/validation.ts b/src/lib/model/validation.ts
similarity index 100%
rename from packages/dashboard-model/src/validation.ts
rename to src/lib/model/validation.ts
diff --git a/apps/web/src/lib/server/dashboard.test.ts b/src/lib/server/dashboard.test.ts
similarity index 53%
rename from apps/web/src/lib/server/dashboard.test.ts
rename to src/lib/server/dashboard.test.ts
index 5d93422..06c45b8 100644
--- a/apps/web/src/lib/server/dashboard.test.ts
+++ b/src/lib/server/dashboard.test.ts
@@ -3,8 +3,8 @@ import { mkdtemp } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { afterEach, describe, expect, test } from "vitest";
-import { dimensionLabDashboardFixture } from "$lib/dashboard-seed/dimensionlab";
-import { genericDashboardFixture } from "@dimensionlab/dashboard-model/fixtures";
+import { dimensionLabDashboardFixture } from "$lib/model/fixtures/dimensionlab";
+import { genericDashboardFixture } from "$lib/model/fixtures/generic";
import { createDashboardStore, type DashboardStore } from "./db/dashboard-store";
import { loadDashboardRuntime } from "./dashboard";
@@ -47,65 +47,6 @@ describe("dashboard runtime loader", () => {
expect(store.listRevisions()).toHaveLength(1);
});
- test("refreshes an existing initial seed when the bundled seed changes", async () => {
- const store = await createTestStore();
- const oldSeed = olderDimensionLabSeed();
- store.seedDashboardIfEmpty(oldSeed, {
- actor: "initial-seed",
- message: "load initial dashboard document",
- });
-
- const runtime = loadDashboardRuntime(store, {
- refreshSeedDocument: true,
- seedDocument: dimensionLabDashboardFixture,
- seedIfEmpty: true,
- });
-
- expect(runtime.state).toBe("ready");
- if (runtime.state !== "ready") throw new Error("expected ready dashboard");
- expect(runtime.document.statusStrips[0]?.items.map((item) => item.id)).toContain(
- "auto-refresh",
- );
- expect(store.listRevisions()).toHaveLength(2);
- expect(store.getActiveDashboard()?.revision.actor).toBe("initial-seed");
- });
-
- test("does not refresh a dashboard after a user-authored revision", async () => {
- const store = await createTestStore();
- const oldSeed = olderDimensionLabSeed();
- store.seedDashboardIfEmpty(oldSeed, {
- actor: "initial-seed",
- message: "load initial dashboard document",
- });
- store.commitDashboard(
- {
- ...oldSeed,
- metadata: {
- ...oldSeed.metadata,
- title: "Custom Dashboard",
- },
- },
- {
- actor: "agent",
- message: "customize dashboard",
- },
- );
-
- const runtime = loadDashboardRuntime(store, {
- refreshSeedDocument: true,
- seedDocument: dimensionLabDashboardFixture,
- seedIfEmpty: true,
- });
-
- expect(runtime.state).toBe("ready");
- if (runtime.state !== "ready") throw new Error("expected ready dashboard");
- expect(runtime.document.metadata.title).toBe("Custom Dashboard");
- expect(runtime.document.statusStrips[0]?.items.map((item) => item.id)).not.toContain(
- "auto-refresh",
- );
- expect(store.listRevisions()).toHaveLength(2);
- });
-
test("returns empty state when no dashboard is active and seeding is disabled", async () => {
const store = await createTestStore();
@@ -128,12 +69,3 @@ async function createTestStore() {
return store;
}
-
-function olderDimensionLabSeed() {
- const document = structuredClone(dimensionLabDashboardFixture);
- document.statusStrips = document.statusStrips.map((strip) => ({
- ...strip,
- items: strip.items.filter((item) => item.id !== "auto-refresh"),
- }));
- return document;
-}
diff --git a/apps/web/src/lib/server/dashboard.ts b/src/lib/server/dashboard.ts
similarity index 66%
rename from apps/web/src/lib/server/dashboard.ts
rename to src/lib/server/dashboard.ts
index 33afa3a..d2bfc39 100644
--- a/apps/web/src/lib/server/dashboard.ts
+++ b/src/lib/server/dashboard.ts
@@ -1,5 +1,5 @@
-import { dimensionLabDashboardFixture } from "$lib/dashboard-seed/dimensionlab";
-import type { DashboardDocument } from "@dimensionlab/dashboard-model";
+import { dimensionLabDashboardFixture } from "$lib/model/fixtures/dimensionlab";
+import type { DashboardDocument } from "$lib/model";
import {
createDashboardStore,
DashboardPersistenceValidationError,
@@ -18,9 +18,6 @@ export interface DashboardRuntimeReady {
document: DashboardDocument;
schemaVersion: string;
currentRevisionId: string;
- liveDatasourceHydration?: {
- enabled: boolean;
- };
}
export interface DashboardRuntimeEmpty {
@@ -46,7 +43,6 @@ export interface DashboardRuntimeInvalid {
}
export interface DashboardRuntimeOptions {
- refreshSeedDocument?: boolean;
seedIfEmpty?: boolean;
seedDocument?: DashboardDocument;
}
@@ -58,20 +54,8 @@ export function loadDashboardRuntime(
const dashboardStore = store || createDashboardStore();
try {
- const seedDocument = options.seedDocument || dimensionLabDashboardFixture;
const active = dashboardStore.getActiveDashboard();
if (active) {
- if (
- options.refreshSeedDocument &&
- shouldRefreshSeedDashboard(active, seedDocument)
- ) {
- const refreshed = dashboardStore.commitDashboard(seedDocument, {
- actor: "initial-seed",
- message: "refresh bundled dashboard document",
- });
- return readyRuntimeState(refreshed.document, refreshed.id);
- }
-
return readyRuntimeState(active.document, active.currentRevisionId);
}
@@ -85,7 +69,7 @@ export function loadDashboardRuntime(
}
const seeded = dashboardStore.seedDashboardIfEmpty(
- seedDocument,
+ options.seedDocument || dimensionLabDashboardFixture,
{
actor: "initial-seed",
message: "load initial dashboard document",
@@ -129,22 +113,3 @@ function invalidRuntimeState(errors: string[]): DashboardRuntimeInvalid {
errors,
};
}
-
-function shouldRefreshSeedDashboard(
- active: { document: DashboardDocument; revision: { actor: string } },
- seedDocument: DashboardDocument,
-): boolean {
- if (active.revision.actor !== "initial-seed") return false;
- if (!isBundledDimensionLabSeed(active.document, seedDocument)) return false;
- return JSON.stringify(active.document) !== JSON.stringify(seedDocument);
-}
-
-function isBundledDimensionLabSeed(
- document: DashboardDocument,
- seedDocument: DashboardDocument,
-): boolean {
- return (
- document.metadata.title === seedDocument.metadata.title &&
- document.metadata.description === seedDocument.metadata.description
- );
-}
diff --git a/apps/web/src/lib/server/db/connection.ts b/src/lib/server/db/connection.ts
similarity index 100%
rename from apps/web/src/lib/server/db/connection.ts
rename to src/lib/server/db/connection.ts
diff --git a/apps/web/src/lib/server/db/dashboard-store.test.ts b/src/lib/server/db/dashboard-store.test.ts
similarity index 97%
rename from apps/web/src/lib/server/db/dashboard-store.test.ts
rename to src/lib/server/db/dashboard-store.test.ts
index c2717f5..ecb80f3 100644
--- a/apps/web/src/lib/server/db/dashboard-store.test.ts
+++ b/src/lib/server/db/dashboard-store.test.ts
@@ -3,8 +3,8 @@ import { mkdtemp } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { afterEach, describe, expect, test, vi } from "vitest";
-import { genericDashboardFixture } from "@dimensionlab/dashboard-model/fixtures";
-import type { DashboardDocument } from "@dimensionlab/dashboard-model";
+import { genericDashboardFixture } from "$lib/model/fixtures/generic";
+import type { DashboardDocument } from "$lib/model";
import {
DashboardPersistenceValidationError,
createDashboardStore,
diff --git a/apps/web/src/lib/server/db/dashboard-store.ts b/src/lib/server/db/dashboard-store.ts
similarity index 99%
rename from apps/web/src/lib/server/db/dashboard-store.ts
rename to src/lib/server/db/dashboard-store.ts
index 3928206..92c7a73 100644
--- a/apps/web/src/lib/server/db/dashboard-store.ts
+++ b/src/lib/server/db/dashboard-store.ts
@@ -3,7 +3,7 @@ import { desc, eq } from "drizzle-orm";
import {
type DashboardDocument,
type DashboardValidationFailure,
-} from "@dimensionlab/dashboard-model";
+} from "$lib/model";
import {
type DashboardDatabaseConnection,
openDashboardDatabase,
diff --git a/apps/web/src/lib/server/db/migrations.ts b/src/lib/server/db/migrations.ts
similarity index 100%
rename from apps/web/src/lib/server/db/migrations.ts
rename to src/lib/server/db/migrations.ts
diff --git a/apps/web/src/lib/server/db/model-migrations.test.ts b/src/lib/server/db/model-migrations.test.ts
similarity index 87%
rename from apps/web/src/lib/server/db/model-migrations.test.ts
rename to src/lib/server/db/model-migrations.test.ts
index 5c863a0..eb16b8f 100644
--- a/apps/web/src/lib/server/db/model-migrations.test.ts
+++ b/src/lib/server/db/model-migrations.test.ts
@@ -1,6 +1,6 @@
import { describe, expect, test } from "vitest";
-import { DASHBOARD_SCHEMA_VERSION } from "@dimensionlab/dashboard-model";
-import { genericDashboardFixture } from "@dimensionlab/dashboard-model/fixtures";
+import { DASHBOARD_SCHEMA_VERSION } from "$lib/model";
+import { genericDashboardFixture } from "$lib/model/fixtures/generic";
import {
UnsupportedDashboardModelVersionError,
migrateDashboardDocumentForPersistence,
diff --git a/apps/web/src/lib/server/db/model-migrations.ts b/src/lib/server/db/model-migrations.ts
similarity index 97%
rename from apps/web/src/lib/server/db/model-migrations.ts
rename to src/lib/server/db/model-migrations.ts
index 72e9c47..92550e6 100644
--- a/apps/web/src/lib/server/db/model-migrations.ts
+++ b/src/lib/server/db/model-migrations.ts
@@ -3,7 +3,7 @@ import {
validateDashboardDocument,
type DashboardDocument,
type DashboardValidationFailure,
-} from "@dimensionlab/dashboard-model";
+} from "$lib/model";
export interface DashboardModelMigrationSuccess {
valid: true;
diff --git a/apps/web/src/lib/server/db/schema.ts b/src/lib/server/db/schema.ts
similarity index 94%
rename from apps/web/src/lib/server/db/schema.ts
rename to src/lib/server/db/schema.ts
index a3a7206..c148035 100644
--- a/apps/web/src/lib/server/db/schema.ts
+++ b/src/lib/server/db/schema.ts
@@ -1,4 +1,4 @@
-import type { DashboardDocument } from "@dimensionlab/dashboard-model";
+import type { DashboardDocument } from "$lib/model";
import { index, integer, sqliteTable, text } from "drizzle-orm/sqlite-core";
export const dashboardDocuments = sqliteTable("dashboard_documents", {
diff --git a/src/lib/ui/components/Badge.svelte b/src/lib/ui/components/Badge.svelte
new file mode 100644
index 0000000..2e9fc8d
--- /dev/null
+++ b/src/lib/ui/components/Badge.svelte
@@ -0,0 +1,14 @@
+
+
+
diff --git a/src/lib/ui/components/Button.svelte b/src/lib/ui/components/Button.svelte
new file mode 100644
index 0000000..fa26d5f
--- /dev/null
+++ b/src/lib/ui/components/Button.svelte
@@ -0,0 +1,85 @@
+
+
+
+ {#if icon}
+
+ {/if}
+ {loading ? "Loading" : label}
+
+
+
diff --git a/src/lib/ui/components/DashboardFrame.svelte b/src/lib/ui/components/DashboardFrame.svelte
new file mode 100644
index 0000000..4a4fff8
--- /dev/null
+++ b/src/lib/ui/components/DashboardFrame.svelte
@@ -0,0 +1,116 @@
+
+
+
+
+
+
+
+
+ {#each dashboard.serviceGroups as group (group.id)}
+
+ {/each}
+
+
+
+
+
+
diff --git a/src/lib/ui/components/DashboardHeader.svelte b/src/lib/ui/components/DashboardHeader.svelte
new file mode 100644
index 0000000..e8d13f2
--- /dev/null
+++ b/src/lib/ui/components/DashboardHeader.svelte
@@ -0,0 +1,71 @@
+
+
+
+
+
diff --git a/src/lib/ui/components/FooterCell.svelte b/src/lib/ui/components/FooterCell.svelte
new file mode 100644
index 0000000..b71cc66
--- /dev/null
+++ b/src/lib/ui/components/FooterCell.svelte
@@ -0,0 +1,90 @@
+
+
+{#snippet cellContent()}
+ {item.label}
+ {item.value}
+{/snippet}
+
+{#if item.link}
+
+{:else}
+
+{/if}
+
+
diff --git a/src/lib/ui/components/FooterStatusCell.svelte b/src/lib/ui/components/FooterStatusCell.svelte
new file mode 100644
index 0000000..997ae3e
--- /dev/null
+++ b/src/lib/ui/components/FooterStatusCell.svelte
@@ -0,0 +1,8 @@
+
+
+
diff --git a/src/lib/ui/components/GridFrame.svelte b/src/lib/ui/components/GridFrame.svelte
new file mode 100644
index 0000000..1368dda
--- /dev/null
+++ b/src/lib/ui/components/GridFrame.svelte
@@ -0,0 +1,30 @@
+
+
+
+ {@render children?.()}
+
+
+
diff --git a/src/lib/ui/components/IconButton.svelte b/src/lib/ui/components/IconButton.svelte
new file mode 100644
index 0000000..ce5ea78
--- /dev/null
+++ b/src/lib/ui/components/IconButton.svelte
@@ -0,0 +1,57 @@
+
+
+
+
+
+
+
diff --git a/src/lib/ui/components/IconGlyph.svelte b/src/lib/ui/components/IconGlyph.svelte
new file mode 100644
index 0000000..81318d6
--- /dev/null
+++ b/src/lib/ui/components/IconGlyph.svelte
@@ -0,0 +1,53 @@
+
+
+
+ {#if name}
+
+ {/if}
+
+
+
diff --git a/src/lib/ui/components/ModuleCard.svelte b/src/lib/ui/components/ModuleCard.svelte
new file mode 100644
index 0000000..f9a0f04
--- /dev/null
+++ b/src/lib/ui/components/ModuleCard.svelte
@@ -0,0 +1,86 @@
+
+
+
+
+ {#if module.title}
+
{module.title}
+ {/if}
+ {#if module.value}
+
{module.value}
+ {/if}
+ {#if module.detail || module.label}
+
{module.detail || module.label}
+ {/if}
+
+ {#if module.icon}
+
+ {/if}
+
+
+
diff --git a/src/lib/ui/components/Panel.svelte b/src/lib/ui/components/Panel.svelte
new file mode 100644
index 0000000..cbc68e3
--- /dev/null
+++ b/src/lib/ui/components/Panel.svelte
@@ -0,0 +1,81 @@
+
+
+
+ {#if title}
+
+ {/if}
+
+ {@render children?.()}
+
+
+
+
diff --git a/src/lib/ui/components/ProgressMeter.svelte b/src/lib/ui/components/ProgressMeter.svelte
new file mode 100644
index 0000000..501d8c7
--- /dev/null
+++ b/src/lib/ui/components/ProgressMeter.svelte
@@ -0,0 +1,58 @@
+
+
+
+ {#if progress !== null}
+
+ {/if}
+
+
+
diff --git a/src/lib/ui/components/Separator.svelte b/src/lib/ui/components/Separator.svelte
new file mode 100644
index 0000000..f2ca968
--- /dev/null
+++ b/src/lib/ui/components/Separator.svelte
@@ -0,0 +1,39 @@
+
+
+
+
+
diff --git a/src/lib/ui/components/ServiceGroupPanel.svelte b/src/lib/ui/components/ServiceGroupPanel.svelte
new file mode 100644
index 0000000..56ccdbb
--- /dev/null
+++ b/src/lib/ui/components/ServiceGroupPanel.svelte
@@ -0,0 +1,8 @@
+
+
+
diff --git a/src/lib/ui/components/ServicePanel.svelte b/src/lib/ui/components/ServicePanel.svelte
new file mode 100644
index 0000000..0b997d7
--- /dev/null
+++ b/src/lib/ui/components/ServicePanel.svelte
@@ -0,0 +1,19 @@
+
+
+
+
+ {#if group.summary?.length}
+
+ {/if}
+ {#each group.services as service (service.id)}
+
+ {/each}
+
+
diff --git a/src/lib/ui/components/ServiceRow.svelte b/src/lib/ui/components/ServiceRow.svelte
new file mode 100644
index 0000000..9a8c9b6
--- /dev/null
+++ b/src/lib/ui/components/ServiceRow.svelte
@@ -0,0 +1,107 @@
+
+
+{#snippet rowContent()}
+
+
+
{service.label}
+
{service.description}
+
+ {#if service.detail}
+
+ {/if}
+{/snippet}
+
+{#if service.link}
+
+ {@render rowContent()}
+
+{:else}
+
+ {@render rowContent()}
+
+{/if}
+
+
diff --git a/src/lib/ui/components/Sparkline.svelte b/src/lib/ui/components/Sparkline.svelte
new file mode 100644
index 0000000..78b96dd
--- /dev/null
+++ b/src/lib/ui/components/Sparkline.svelte
@@ -0,0 +1,58 @@
+
+
+
+ {#if points}
+
+ {/if}
+
+
+
+
+
diff --git a/src/lib/ui/components/StatusBadge.svelte b/src/lib/ui/components/StatusBadge.svelte
new file mode 100644
index 0000000..a0fa058
--- /dev/null
+++ b/src/lib/ui/components/StatusBadge.svelte
@@ -0,0 +1,52 @@
+
+
+{label}
+
+
diff --git a/src/lib/ui/components/StatusStrip.svelte b/src/lib/ui/components/StatusStrip.svelte
new file mode 100644
index 0000000..a11b786
--- /dev/null
+++ b/src/lib/ui/components/StatusStrip.svelte
@@ -0,0 +1,34 @@
+
+
+
+ {#each items as item (item.id)}
+
+ {/each}
+
+
+
diff --git a/src/lib/ui/components/SystemState.svelte b/src/lib/ui/components/SystemState.svelte
new file mode 100644
index 0000000..4395618
--- /dev/null
+++ b/src/lib/ui/components/SystemState.svelte
@@ -0,0 +1,67 @@
+
+
+
+
+
+
{title}
+ {#if detail}
+
{detail}
+ {/if}
+
+
+
+
diff --git a/src/lib/ui/components/TelemetryCard.svelte b/src/lib/ui/components/TelemetryCard.svelte
new file mode 100644
index 0000000..f621709
--- /dev/null
+++ b/src/lib/ui/components/TelemetryCard.svelte
@@ -0,0 +1,158 @@
+
+
+
+
+ {#if card.icon}
+
+ {/if}
+ {card.label}
+
+ {value}
+ {#if progress !== null}
+
+
+
+ {/if}
+ {#if card.sparkline?.length}
+
+
+
+ {/if}
+ {#if card.detail || card.description}
+ {card.detail || card.description}
+ {/if}
+
+
+
+
+
diff --git a/src/lib/ui/components/TelemetryGrid.svelte b/src/lib/ui/components/TelemetryGrid.svelte
new file mode 100644
index 0000000..aead537
--- /dev/null
+++ b/src/lib/ui/components/TelemetryGrid.svelte
@@ -0,0 +1,22 @@
+
+
+
+ {#each cards as card (card.id)}
+
+ {/each}
+
+
+
diff --git a/src/lib/ui/components/TelemetryStrip.svelte b/src/lib/ui/components/TelemetryStrip.svelte
new file mode 100644
index 0000000..a8beee3
--- /dev/null
+++ b/src/lib/ui/components/TelemetryStrip.svelte
@@ -0,0 +1,8 @@
+
+
+
diff --git a/src/lib/ui/components/WeatherModule.svelte b/src/lib/ui/components/WeatherModule.svelte
new file mode 100644
index 0000000..295f195
--- /dev/null
+++ b/src/lib/ui/components/WeatherModule.svelte
@@ -0,0 +1,8 @@
+
+
+
diff --git a/src/lib/ui/components/render.test.ts b/src/lib/ui/components/render.test.ts
new file mode 100644
index 0000000..fa55ecb
--- /dev/null
+++ b/src/lib/ui/components/render.test.ts
@@ -0,0 +1,137 @@
+import { render } from "svelte/server";
+import { describe, expect, test } from "vitest";
+import Button from "./Button.svelte";
+import DashboardFrame from "./DashboardFrame.svelte";
+import FooterCell from "./FooterCell.svelte";
+import IconButton from "./IconButton.svelte";
+import ServiceRow from "./ServiceRow.svelte";
+import TelemetryCard from "./TelemetryCard.svelte";
+import { dashboardPreviewFixtures } from "../fixtures";
+
+describe("dashboard UI components", () => {
+ test("renders the primary generic dashboard fixture", () => {
+ const { body } = render(DashboardFrame, {
+ props: {
+ dashboard: dashboardPreviewFixtures.primary,
+ },
+ });
+
+ expect(body).toContain("Operations Console");
+ expect(body).toContain("Core Throughput");
+ expect(body).toContain("Queue Workers");
+ expect(body).toContain("data-icon-name=\"mdi:server-network\"");
+ expect(body).toContain("data-severity=\"warning\"");
+ expect(body).not.toContain("dashboard-title");
+ });
+
+ test("renders another generic dashboard fixture through the same component", () => {
+ const { body } = render(DashboardFrame, {
+ props: {
+ dashboard: dashboardPreviewFixtures.secondary,
+ },
+ });
+
+ expect(body).toContain("Support Desk");
+ expect(body).toContain("Response Window");
+ expect(body).toContain("Regional Nodes");
+ expect(body).toContain("data-icon-name=\"mdi:headset\"");
+ expect(body).toContain("data-severity=\"loading\"");
+ });
+
+ test("renders optional service and status links as focusable anchors", () => {
+ const service = render(ServiceRow, {
+ props: {
+ service: {
+ id: "linked-service",
+ label: "Linked Service",
+ description: "Generic linked destination",
+ severity: "ok",
+ detail: "ready",
+ link: { href: "https://example.test/service", external: true },
+ },
+ },
+ });
+ const footer = render(FooterCell, {
+ props: {
+ item: {
+ id: "linked-status",
+ label: "Linked Status",
+ value: "open",
+ severity: "neutral",
+ link: { href: "https://example.test/status" },
+ },
+ },
+ });
+
+ expect(service.body).toContain(" {
+ const { body } = render(DashboardFrame, {
+ props: {
+ 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 = render(TelemetryCard, {
+ props: {
+ card: {
+ id: "bytes",
+ label: "Bytes Metric",
+ value: { kind: "bytes", value: 2048 },
+ severity: "neutral",
+ },
+ },
+ });
+ const withProgress = render(TelemetryCard, {
+ props: {
+ card: {
+ id: "bytes-with-progress",
+ label: "Bytes With Progress",
+ value: { kind: "bytes", value: 2048 },
+ progress: 42,
+ severity: "neutral",
+ },
+ },
+ });
+
+ expect(withoutProgress.body).not.toContain("telemetry-card__bar");
+ expect(withProgress.body).toContain("--metric-progress: 42%");
+ });
+
+ test("base button controls forward native attributes", () => {
+ const button = render(Button, {
+ props: {
+ label: "Refresh",
+ id: "refresh-action",
+ class: "custom-action",
+ "aria-controls": "refresh-target",
+ },
+ });
+ const iconButton = render(IconButton, {
+ props: {
+ icon: "mdi:refresh",
+ label: "Refresh status",
+ id: "refresh-icon-action",
+ class: "custom-icon-action",
+ "aria-expanded": "false",
+ },
+ });
+
+ expect(button.body).toContain("id=\"refresh-action\"");
+ expect(button.body).toContain("class=\"ui-button custom-action ");
+ expect(button.body).toContain("aria-controls=\"refresh-target\"");
+ expect(iconButton.body).toContain("id=\"refresh-icon-action\"");
+ expect(iconButton.body).toContain("class=\"icon-button custom-icon-action ");
+ expect(iconButton.body).toContain("aria-expanded=\"false\"");
+ });
+});
diff --git a/src/lib/ui/content-boundary.test.ts b/src/lib/ui/content-boundary.test.ts
new file mode 100644
index 0000000..6613644
--- /dev/null
+++ b/src/lib/ui/content-boundary.test.ts
@@ -0,0 +1,49 @@
+import { readdirSync, readFileSync, statSync } from "node:fs";
+import { join } from "node:path";
+import { describe, expect, test } from "vitest";
+
+const forbiddenTerms = [
+ "dimensionlab",
+ "dimension lab",
+ "vaultwarden",
+ "forgejo",
+ "grafana",
+ "uptime kuma",
+ "prometheus",
+ "backrest",
+ "open webui",
+ "comfyui",
+ "adminer",
+ "cockpit",
+ "ollama",
+];
+
+describe("UI content boundary", () => {
+ test("keeps environment-specific content out of reusable UI source", () => {
+ const source = readUiSource(join(process.cwd(), "src", "lib", "ui"));
+ const normalized = source.toLowerCase();
+
+ expect(
+ forbiddenTerms.filter((term) => normalized.includes(term)),
+ ).toEqual([]);
+ });
+
+ test("keeps icon rendering driven by icon identifiers", () => {
+ const source = readUiSource(join(process.cwd(), "src", "lib", "ui"));
+
+ expect(source).not.toContain("@iconify-json/");
+ expect(source).not.toContain("/icons/");
+ });
+});
+
+function readUiSource(path: string): string {
+ const stats = statSync(path);
+ if (stats.isFile()) {
+ if (path.endsWith(".test.ts")) return "";
+ return readFileSync(path, "utf8");
+ }
+
+ return readdirSync(path)
+ .map((entry) => readUiSource(join(path, entry)))
+ .join("\n");
+}
diff --git a/src/lib/ui/fixtures.ts b/src/lib/ui/fixtures.ts
new file mode 100644
index 0000000..b8008c1
--- /dev/null
+++ b/src/lib/ui/fixtures.ts
@@ -0,0 +1,132 @@
+import type { UiDashboardPreview } from "./types";
+
+export const dashboardPreviewFixtures: Record = {
+ primary: {
+ eyebrow: "Command Surface",
+ title: "Operations Console",
+ subtitle: "Capacity, latency, and queue health",
+ telemetry: [
+ metric("core-throughput", "Core Throughput", "mdi:server-network", 82, "ok"),
+ metric("edge-cache", "Edge Cache", "mdi:database-clock", 67, "neutral"),
+ metric("queue-depth", "Queue Depth", "mdi:tray-full", 74, "warning"),
+ metric("error-budget", "Error Budget", "mdi:chart-timeline-variant", 18, "danger"),
+ metric("build-latency", "Build Latency", "mdi:timer-sand", 23, "stale"),
+ metric("worker-load", "Worker Load", "mdi:cpu-64-bit", 55, "ok"),
+ ],
+ modules: [
+ {
+ id: "ambient-conditions",
+ title: "Local Node",
+ value: "21.4 C",
+ detail: "clear window",
+ icon: "mdi:weather-partly-cloudy",
+ severity: "ok",
+ },
+ ],
+ serviceGroups: [
+ {
+ id: "queue-workers",
+ title: "Queue Workers",
+ services: [
+ service("ingest", "Ingest", "Event intake pipeline", "mdi:arrow-collapse-down", "ok", "1.252 ms"),
+ service("scheduler", "Scheduler", "Timed job coordinator", "mdi:calendar-clock", "warning", "1.487 ms"),
+ service("archive", "Archive", "Cold storage transfer", "mdi:archive-arrow-down", "ok", "1.301 ms"),
+ ],
+ },
+ {
+ id: "regional-nodes",
+ title: "Regional Nodes",
+ services: [
+ service("north", "North", "Primary traffic cell", "mdi:access-point", "ok", "899 ms"),
+ service("west", "West", "Replica traffic cell", "mdi:access-point-network", "neutral", "1.118 ms"),
+ service("south", "South", "Maintenance window", "mdi:wrench-clock", "stale", "paused"),
+ ],
+ },
+ {
+ id: "control-plane",
+ title: "Control Plane",
+ services: [
+ service("identity", "Identity", "Access token exchange", "mdi:account-key", "ok", "721 ms"),
+ service("policy", "Policy", "Rules evaluation", "mdi:shield-check", "ok", "812 ms"),
+ service("audit", "Audit", "Event ledger writer", "mdi:text-box-search", "warning", "1.442 ms"),
+ ],
+ },
+ ],
+ statusItems: [
+ { id: "system", label: "System", value: "Nominal", severity: "ok" },
+ { id: "sync", label: "Last Sync", value: "2 minutes ago", severity: "stale" },
+ { id: "uptime", label: "Uptime", value: "14d 08h", severity: "neutral" },
+ { id: "refresh", label: "Refresh", value: "15s", severity: "neutral" },
+ ],
+ 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 };
+}
diff --git a/src/lib/ui/format.ts b/src/lib/ui/format.ts
new file mode 100644
index 0000000..4a8a5f1
--- /dev/null
+++ b/src/lib/ui/format.ts
@@ -0,0 +1,41 @@
+import type { UiMetricValue } from "./types";
+
+const byteUnits = ["B", "KB", "MB", "GB", "TB"];
+
+export function formatMetricValue(metric: UiMetricValue): string {
+ if (metric.kind === "text") return String(metric.value);
+
+ const value = typeof metric.value === "number" ? metric.value : Number(metric.value);
+ const precision = metric.precision ?? inferPrecision(value);
+ const unit = metric.unit ?? defaultUnit(metric.kind);
+
+ if (metric.kind === "bytes") return formatBytes(value, precision);
+ return `${value.toFixed(precision)}${unit ? ` ${unit}` : ""}`;
+}
+
+export function clampPercent(value = 0): number {
+ if (!Number.isFinite(value)) return 0;
+ return Math.max(0, Math.min(100, value));
+}
+
+function defaultUnit(kind: UiMetricValue["kind"]): string {
+ if (kind === "percent") return "%";
+ if (kind === "temperature") return "C";
+ if (kind === "latency") return "ms";
+ return "";
+}
+
+function inferPrecision(value: number): number {
+ return Number.isInteger(value) ? 0 : 1;
+}
+
+function formatBytes(value: number, precision: number): string {
+ let size = value;
+ let index = 0;
+ while (size >= 1024 && index < byteUnits.length - 1) {
+ size /= 1024;
+ index += 1;
+ }
+
+ return `${size.toFixed(precision)} ${byteUnits[index]}`;
+}
diff --git a/src/lib/ui/index.ts b/src/lib/ui/index.ts
new file mode 100644
index 0000000..8c312fc
--- /dev/null
+++ b/src/lib/ui/index.ts
@@ -0,0 +1,37 @@
+export { default as Badge } from "./components/Badge.svelte";
+export { default as Button } from "./components/Button.svelte";
+export { default as DashboardHeader } from "./components/DashboardHeader.svelte";
+export { default as DashboardFrame } from "./components/DashboardFrame.svelte";
+export { default as FooterCell } from "./components/FooterCell.svelte";
+export { default as FooterStatusCell } from "./components/FooterStatusCell.svelte";
+export { default as GridFrame } from "./components/GridFrame.svelte";
+export { default as IconGlyph } from "./components/IconGlyph.svelte";
+export { default as IconButton } from "./components/IconButton.svelte";
+export { default as ModuleCard } from "./components/ModuleCard.svelte";
+export { default as Panel } from "./components/Panel.svelte";
+export { default as ProgressMeter } from "./components/ProgressMeter.svelte";
+export { default as Separator } from "./components/Separator.svelte";
+export { default as ServiceGroupPanel } from "./components/ServiceGroupPanel.svelte";
+export { default as ServicePanel } from "./components/ServicePanel.svelte";
+export { default as ServiceRow } from "./components/ServiceRow.svelte";
+export { default as Sparkline } from "./components/Sparkline.svelte";
+export { default as StatusBadge } from "./components/StatusBadge.svelte";
+export { default as StatusStrip } from "./components/StatusStrip.svelte";
+export { default as SystemState } from "./components/SystemState.svelte";
+export { default as TelemetryCard } from "./components/TelemetryCard.svelte";
+export { default as TelemetryGrid } from "./components/TelemetryGrid.svelte";
+export { default as TelemetryStrip } from "./components/TelemetryStrip.svelte";
+export { default as WeatherModule } from "./components/WeatherModule.svelte";
+export { dashboardPreviewFixtures } from "./fixtures";
+export { dashboardDocumentToUiDashboard } from "./model-renderer";
+export type {
+ UiDashboardPreview,
+ UiLink,
+ UiMetricValue,
+ UiModuleBlock,
+ UiServiceGroup,
+ UiServiceRow,
+ UiSeverity,
+ UiStatusItem,
+ UiTelemetryCard,
+} from "./types";
diff --git a/apps/web/src/lib/ui-adapter/model-renderer.test.ts b/src/lib/ui/model-renderer.test.ts
similarity index 78%
rename from apps/web/src/lib/ui-adapter/model-renderer.test.ts
rename to src/lib/ui/model-renderer.test.ts
index 2f8178d..def9d50 100644
--- a/apps/web/src/lib/ui-adapter/model-renderer.test.ts
+++ b/src/lib/ui/model-renderer.test.ts
@@ -1,15 +1,13 @@
import { readFileSync } from "node:fs";
import { join } from "node:path";
import { describe, expect, test } from "vitest";
-import { type DashboardDocument } from "@dimensionlab/dashboard-model";
-import { dimensionLabDashboardFixture } from "$lib/dashboard-seed/dimensionlab";
-import { genericDashboardFixture } from "@dimensionlab/dashboard-model/fixtures";
+import {
+ type DashboardDocument,
+} from "$lib/model";
+import { dimensionLabDashboardFixture } from "$lib/model/fixtures/dimensionlab";
+import { genericDashboardFixture } from "$lib/model/fixtures/generic";
import { dashboardDocumentToUiDashboard } from "./model-renderer";
-const appRoot = process.cwd().endsWith(`${join("apps", "web")}`)
- ? process.cwd()
- : join(process.cwd(), "apps", "web");
-
describe("dashboard model renderer", () => {
test("projects the Dimension Lab model into UI component props", () => {
const dashboard = dashboardDocumentToUiDashboard(dimensionLabDashboardFixture);
@@ -27,9 +25,6 @@ describe("dashboard model renderer", () => {
expect(dashboard.serviceGroups.map((group) => group.id)).toEqual(
dimensionLabDashboardFixture.layout.serviceGroups,
);
- expect(dashboard.serviceGroups.find((group) => group.id === "runtime-health")?.layout).toBe(
- "grid",
- );
expect(dashboard.modules.map((module) => module.id)).toEqual([
"weather-amsterdam",
"runtime-health-summary",
@@ -39,7 +34,6 @@ describe("dashboard model renderer", () => {
"footer-status:last-sync",
"footer-status:uptime",
"footer-status:load-avg",
- "footer-status:auto-refresh",
]);
expect(dashboard.statusStripId).toBe("footer-status");
});
@@ -88,13 +82,7 @@ describe("dashboard model renderer", () => {
});
test("does not hardcode environment-specific content in mapper source", () => {
- const source = readFileSync(
- join(appRoot, "src/lib/ui-adapter/model-renderer.ts"),
- "utf8",
- )
- .toLowerCase()
- .replaceAll("@dimensionlab/dashboard-model", "@internal/dashboard-model")
- .replaceAll("@dimensionlab/ui", "@internal/ui");
+ const source = readFileSync(join(process.cwd(), "src/lib/ui/model-renderer.ts"), "utf8").toLowerCase();
expect(source).not.toContain("dimension");
expect(source).not.toContain("vaultwarden");
diff --git a/apps/web/src/lib/ui-adapter/model-renderer.ts b/src/lib/ui/model-renderer.ts
similarity index 96%
rename from apps/web/src/lib/ui-adapter/model-renderer.ts
rename to src/lib/ui/model-renderer.ts
index 7ac1912..96e58f3 100644
--- a/apps/web/src/lib/ui-adapter/model-renderer.ts
+++ b/src/lib/ui/model-renderer.ts
@@ -6,7 +6,7 @@ import type {
StatusItem,
StatusStrip,
TelemetryCard,
-} from "@dimensionlab/dashboard-model";
+} from "$lib/model";
import type {
UiDashboardPreview,
UiModuleBlock,
@@ -14,7 +14,7 @@ import type {
UiServiceRow,
UiStatusItem,
UiTelemetryCard,
-} from "@dimensionlab/ui";
+} from "./types";
export function dashboardDocumentToUiDashboard(
document: DashboardDocument,
@@ -58,7 +58,6 @@ function telemetryToUi(card: TelemetryCard): UiTelemetryCard {
function serviceGroupToUi(group: ServiceGroup): UiServiceGroup {
return {
id: group.id,
- layout: group.layout,
title: group.title,
services: group.services.map(serviceToUi),
};
diff --git a/src/lib/ui/stories/Badge.stories.svelte b/src/lib/ui/stories/Badge.stories.svelte
new file mode 100644
index 0000000..ac557ff
--- /dev/null
+++ b/src/lib/ui/stories/Badge.stories.svelte
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
+
diff --git a/src/lib/ui/stories/Button.stories.svelte b/src/lib/ui/stories/Button.stories.svelte
new file mode 100644
index 0000000..7c34c3e
--- /dev/null
+++ b/src/lib/ui/stories/Button.stories.svelte
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/src/lib/ui/stories/DashboardFrame.stories.svelte b/src/lib/ui/stories/DashboardFrame.stories.svelte
new file mode 100644
index 0000000..c31627b
--- /dev/null
+++ b/src/lib/ui/stories/DashboardFrame.stories.svelte
@@ -0,0 +1,15 @@
+
+
+
+
+
diff --git a/src/lib/ui/stories/DashboardHeader.stories.svelte b/src/lib/ui/stories/DashboardHeader.stories.svelte
new file mode 100644
index 0000000..10d2133
--- /dev/null
+++ b/src/lib/ui/stories/DashboardHeader.stories.svelte
@@ -0,0 +1,31 @@
+
+
+
+
+
diff --git a/src/lib/ui/stories/DashboardOnePager.stories.svelte b/src/lib/ui/stories/DashboardOnePager.stories.svelte
new file mode 100644
index 0000000..8af2c5e
--- /dev/null
+++ b/src/lib/ui/stories/DashboardOnePager.stories.svelte
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
+
diff --git a/src/lib/ui/stories/FocusPreview.svelte b/src/lib/ui/stories/FocusPreview.svelte
new file mode 100644
index 0000000..514691f
--- /dev/null
+++ b/src/lib/ui/stories/FocusPreview.svelte
@@ -0,0 +1,33 @@
+
+
+
+ {@render children?.()}
+
+
+
diff --git a/src/lib/ui/stories/FooterCell.stories.svelte b/src/lib/ui/stories/FooterCell.stories.svelte
new file mode 100644
index 0000000..cb4a08e
--- /dev/null
+++ b/src/lib/ui/stories/FooterCell.stories.svelte
@@ -0,0 +1,16 @@
+
+
+
+
+
+
diff --git a/src/lib/ui/stories/FooterStatusCell.stories.svelte b/src/lib/ui/stories/FooterStatusCell.stories.svelte
new file mode 100644
index 0000000..578cb11
--- /dev/null
+++ b/src/lib/ui/stories/FooterStatusCell.stories.svelte
@@ -0,0 +1,16 @@
+
+
+
+
+
+
diff --git a/src/lib/ui/stories/GridFrame.stories.svelte b/src/lib/ui/stories/GridFrame.stories.svelte
new file mode 100644
index 0000000..d8be595
--- /dev/null
+++ b/src/lib/ui/stories/GridFrame.stories.svelte
@@ -0,0 +1,37 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/lib/ui/stories/IconButton.stories.svelte b/src/lib/ui/stories/IconButton.stories.svelte
new file mode 100644
index 0000000..faa05b6
--- /dev/null
+++ b/src/lib/ui/stories/IconButton.stories.svelte
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/lib/ui/stories/IconGlyph.stories.svelte b/src/lib/ui/stories/IconGlyph.stories.svelte
new file mode 100644
index 0000000..043e7b3
--- /dev/null
+++ b/src/lib/ui/stories/IconGlyph.stories.svelte
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/lib/ui/stories/ModuleCard.stories.svelte b/src/lib/ui/stories/ModuleCard.stories.svelte
new file mode 100644
index 0000000..890a3ca
--- /dev/null
+++ b/src/lib/ui/stories/ModuleCard.stories.svelte
@@ -0,0 +1,15 @@
+
+
+
+
+
diff --git a/src/lib/ui/stories/Panel.stories.svelte b/src/lib/ui/stories/Panel.stories.svelte
new file mode 100644
index 0000000..c3d3a80
--- /dev/null
+++ b/src/lib/ui/stories/Panel.stories.svelte
@@ -0,0 +1,42 @@
+
+
+
+
+ Reusable panel content.
+
+
+
+
+ Compact repeated content.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/lib/ui/stories/ProgressMeter.stories.svelte b/src/lib/ui/stories/ProgressMeter.stories.svelte
new file mode 100644
index 0000000..861009f
--- /dev/null
+++ b/src/lib/ui/stories/ProgressMeter.stories.svelte
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
diff --git a/src/lib/ui/stories/Separator.stories.svelte b/src/lib/ui/stories/Separator.stories.svelte
new file mode 100644
index 0000000..3f2f6e3
--- /dev/null
+++ b/src/lib/ui/stories/Separator.stories.svelte
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+ Generic section one
+
+ Generic section two
+
+
diff --git a/src/lib/ui/stories/ServiceGroupPanel.stories.svelte b/src/lib/ui/stories/ServiceGroupPanel.stories.svelte
new file mode 100644
index 0000000..51c48bd
--- /dev/null
+++ b/src/lib/ui/stories/ServiceGroupPanel.stories.svelte
@@ -0,0 +1,16 @@
+
+
+
+
+
+
diff --git a/src/lib/ui/stories/ServicePanel.stories.svelte b/src/lib/ui/stories/ServicePanel.stories.svelte
new file mode 100644
index 0000000..059b63b
--- /dev/null
+++ b/src/lib/ui/stories/ServicePanel.stories.svelte
@@ -0,0 +1,16 @@
+
+
+
+
+
+
diff --git a/src/lib/ui/stories/ServiceRow.stories.svelte b/src/lib/ui/stories/ServiceRow.stories.svelte
new file mode 100644
index 0000000..7be9f19
--- /dev/null
+++ b/src/lib/ui/stories/ServiceRow.stories.svelte
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/lib/ui/stories/Sparkline.stories.svelte b/src/lib/ui/stories/Sparkline.stories.svelte
new file mode 100644
index 0000000..62744bd
--- /dev/null
+++ b/src/lib/ui/stories/Sparkline.stories.svelte
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
diff --git a/src/lib/ui/stories/StatusBadge.stories.svelte b/src/lib/ui/stories/StatusBadge.stories.svelte
new file mode 100644
index 0000000..1429ceb
--- /dev/null
+++ b/src/lib/ui/stories/StatusBadge.stories.svelte
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
+
+
diff --git a/src/lib/ui/stories/StatusStrip.stories.svelte b/src/lib/ui/stories/StatusStrip.stories.svelte
new file mode 100644
index 0000000..7b6d22d
--- /dev/null
+++ b/src/lib/ui/stories/StatusStrip.stories.svelte
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+
+
diff --git a/src/lib/ui/stories/SystemState.stories.svelte b/src/lib/ui/stories/SystemState.stories.svelte
new file mode 100644
index 0000000..0e28282
--- /dev/null
+++ b/src/lib/ui/stories/SystemState.stories.svelte
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
diff --git a/src/lib/ui/stories/TelemetryCard.stories.svelte b/src/lib/ui/stories/TelemetryCard.stories.svelte
new file mode 100644
index 0000000..004f47b
--- /dev/null
+++ b/src/lib/ui/stories/TelemetryCard.stories.svelte
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/lib/ui/stories/TelemetryGrid.stories.svelte b/src/lib/ui/stories/TelemetryGrid.stories.svelte
new file mode 100644
index 0000000..8054cc9
--- /dev/null
+++ b/src/lib/ui/stories/TelemetryGrid.stories.svelte
@@ -0,0 +1,15 @@
+
+
+
+
+
diff --git a/src/lib/ui/stories/TelemetryStrip.stories.svelte b/src/lib/ui/stories/TelemetryStrip.stories.svelte
new file mode 100644
index 0000000..5eb01b5
--- /dev/null
+++ b/src/lib/ui/stories/TelemetryStrip.stories.svelte
@@ -0,0 +1,15 @@
+
+
+
+
+
diff --git a/src/lib/ui/stories/WeatherModule.stories.svelte b/src/lib/ui/stories/WeatherModule.stories.svelte
new file mode 100644
index 0000000..de86e73
--- /dev/null
+++ b/src/lib/ui/stories/WeatherModule.stories.svelte
@@ -0,0 +1,16 @@
+
+
+
+
+
+
diff --git a/src/lib/ui/stories/story-data.ts b/src/lib/ui/stories/story-data.ts
new file mode 100644
index 0000000..4085dac
--- /dev/null
+++ b/src/lib/ui/stories/story-data.ts
@@ -0,0 +1,258 @@
+import { dashboardPreviewFixtures } from "../fixtures";
+import type {
+ UiDashboardPreview,
+ UiModuleBlock,
+ UiServiceGroup,
+ UiServiceRow,
+ UiSeverity,
+ UiStatusItem,
+ UiTelemetryCard,
+} from "../types";
+
+export const primaryDashboard = dashboardPreviewFixtures.primary;
+export const secondaryDashboard = dashboardPreviewFixtures.secondary;
+
+export const emptyDashboard: UiDashboardPreview = {
+ eyebrow: "Preview Surface",
+ title: "Empty Console",
+ subtitle: "No records available",
+ telemetry: [],
+ modules: [],
+ serviceGroups: [],
+ statusItems: [],
+};
+
+export const moduleBlocks: Record = {
+ compact: {
+ id: "module-compact",
+ title: "Local Node",
+ value: "21.4 C",
+ detail: "static sample",
+ icon: "mdi:weather-partly-cloudy",
+ severity: "ok",
+ },
+ unavailable: {
+ id: "module-unavailable",
+ title: "External Signal",
+ value: "n/a",
+ detail: "fallback value",
+ icon: "mdi:cloud-off-outline",
+ severity: "unavailable",
+ },
+ long: {
+ id: "module-long",
+ title: "Regional Aggregate Signal",
+ value: "manual review queued",
+ detail: "long generic label wraps without layout shift",
+ icon: "mdi:map-marker-radius-outline",
+ severity: "warning",
+ },
+};
+
+export const telemetryCards: Record = {
+ percent: {
+ id: "telemetry-percent",
+ label: "Capacity Used",
+ value: { kind: "percent", value: 82 },
+ progress: 82,
+ icon: "mdi:gauge",
+ severity: "ok",
+ detail: "static sample",
+ sparkline: [24, 36, 28, 44, 52, 82],
+ },
+ bytes: {
+ id: "telemetry-bytes",
+ label: "Data Volume",
+ value: { kind: "bytes", value: 982451653, precision: 1 },
+ icon: "mdi:database",
+ severity: "neutral",
+ detail: "static sample",
+ sparkline: [8, 12, 20, 18, 24, 29],
+ },
+ temperature: {
+ id: "telemetry-temperature",
+ label: "Node Temperature",
+ value: { kind: "temperature", value: 21.4, precision: 1 },
+ icon: "mdi:thermometer",
+ severity: "neutral",
+ detail: "static sample",
+ sparkline: [18, 20, 19, 21, 20, 21.4],
+ },
+ latency: {
+ id: "telemetry-latency",
+ label: "Median Latency",
+ value: { kind: "latency", value: 87 },
+ icon: "mdi:timer-outline",
+ severity: "warning",
+ detail: "static sample",
+ sparkline: [45, 51, 72, 63, 80, 87],
+ },
+ unavailable: {
+ id: "telemetry-unavailable",
+ label: "Remote Signal",
+ value: { kind: "text", value: "n/a" },
+ icon: "mdi:cloud-off-outline",
+ severity: "unavailable",
+ detail: "fallback value",
+ },
+ stale: {
+ id: "telemetry-stale",
+ label: "Sync Age",
+ value: { kind: "text", value: "stale" },
+ icon: "mdi:clock-alert-outline",
+ severity: "stale",
+ detail: "cached sample",
+ sparkline: [22, 22, 22, 22, 22, 22],
+ },
+ danger: {
+ id: "telemetry-danger",
+ label: "Error Budget",
+ value: { kind: "percent", value: 7 },
+ progress: 7,
+ icon: "mdi:alert-octagon-outline",
+ severity: "danger",
+ detail: "threshold breached",
+ sparkline: [56, 41, 34, 20, 13, 7],
+ },
+ loading: {
+ id: "telemetry-loading",
+ label: "Pending Refresh",
+ value: { kind: "text", value: "sync" },
+ icon: "mdi:progress-clock",
+ severity: "loading",
+ detail: "waiting for update",
+ },
+ long: {
+ id: "telemetry-long",
+ label: "Very Long Generic Source Label That Must Wrap",
+ value: { kind: "percent", value: 63 },
+ progress: 63,
+ icon: "mdi:chart-box-outline",
+ severity: "neutral",
+ detail: "long source label",
+ sparkline: [14, 20, 28, 31, 49, 63],
+ },
+};
+
+export const serviceRows: Record = {
+ normal: row("service-normal", "Ingest", "Event intake pipeline", "mdi:arrow-collapse-down", "ok", "1.252 ms", {
+ href: "#ingest",
+ label: "Open ingest",
+ }),
+ down: row("service-down", "Replica", "Replica traffic cell", "mdi:access-point-off", "danger", "down"),
+ degraded: row("service-degraded", "Scheduler", "Timed job coordinator", "mdi:calendar-clock", "warning", "1.487 ms"),
+ stale: row("service-stale", "Archive", "Cold storage transfer", "mdi:archive-clock", "stale", "paused"),
+ noLink: row("service-no-link", "Policy", "Rules evaluation", "mdi:shield-check", "neutral", "manual"),
+ long: row(
+ "service-long",
+ "Long Generic Service Name That Wraps Cleanly",
+ "Long generic service description with enough detail to exercise wrapping in constrained panels",
+ "mdi:text-box-search-outline",
+ "warning",
+ "review queued",
+ ),
+};
+
+export const serviceGroups: Record = {
+ short: {
+ id: "group-short",
+ title: "Short List",
+ services: [serviceRows.normal, serviceRows.degraded],
+ },
+ long: {
+ id: "group-long",
+ title: "Long List",
+ services: [
+ serviceRows.normal,
+ serviceRows.degraded,
+ serviceRows.stale,
+ serviceRows.down,
+ serviceRows.noLink,
+ serviceRows.long,
+ ],
+ },
+ empty: {
+ id: "group-empty",
+ title: "Empty Group",
+ services: [],
+ },
+ mixed: {
+ id: "group-mixed",
+ title: "Mixed Statuses",
+ services: [serviceRows.normal, serviceRows.down, serviceRows.degraded, serviceRows.stale],
+ summary: [
+ { id: "summary-ok", label: "Ok", value: "3", severity: "ok" },
+ { id: "summary-warn", label: "Warn", value: "1", severity: "warning" },
+ { id: "summary-down", label: "Down", value: "1", severity: "danger" },
+ ],
+ },
+};
+
+export const statusItems: Record = {
+ ok: { id: "status-ok", label: "System", value: "Operational", severity: "ok" },
+ degraded: { id: "status-degraded", label: "Routing", value: "Degraded", severity: "warning" },
+ incident: { id: "status-incident", label: "Incident", value: "Active", severity: "danger" },
+ syncing: { id: "status-syncing", label: "Refresh", value: "Syncing", severity: "loading" },
+ stale: { id: "status-stale", label: "Last Sync", value: "18 minutes ago", severity: "stale" },
+ action: {
+ id: "status-action",
+ label: "Action",
+ value: "Inspect",
+ severity: "neutral",
+ link: { href: "#inspect", label: "Inspect status" },
+ },
+ long: {
+ id: "status-long",
+ label: "Long Generic Status Label",
+ value: "long generic status value wraps",
+ severity: "neutral",
+ },
+};
+
+export const eightTelemetryCards = [
+ telemetryCards.percent,
+ telemetryCards.bytes,
+ telemetryCards.temperature,
+ telemetryCards.latency,
+ telemetryCards.unavailable,
+ telemetryCards.stale,
+ telemetryCards.danger,
+ telemetryCards.loading,
+];
+
+export const sixteenTelemetryCards = Array.from({ length: 16 }, (_, index) => {
+ const card = eightTelemetryCards[index % eightTelemetryCards.length];
+ return {
+ ...card,
+ id: `${card.id}-${index}`,
+ label: `${card.label} ${index + 1}`,
+ };
+});
+
+export const mixedStatusStrip = [
+ statusItems.ok,
+ statusItems.degraded,
+ statusItems.incident,
+ statusItems.syncing,
+ statusItems.stale,
+];
+
+export const fullCompositionDashboard: UiDashboardPreview = {
+ ...primaryDashboard,
+ telemetry: eightTelemetryCards,
+ modules: [moduleBlocks.compact, moduleBlocks.unavailable],
+ serviceGroups: [serviceGroups.mixed, serviceGroups.short, serviceGroups.long],
+ statusItems: mixedStatusStrip,
+};
+
+function row(
+ id: string,
+ label: string,
+ description: string,
+ icon: string,
+ severity: UiSeverity,
+ detail: string,
+ link?: UiServiceRow["link"],
+): UiServiceRow {
+ return { id, label, description, icon, severity, detail, link };
+}
diff --git a/src/lib/ui/storybook.test.ts b/src/lib/ui/storybook.test.ts
new file mode 100644
index 0000000..226eea8
--- /dev/null
+++ b/src/lib/ui/storybook.test.ts
@@ -0,0 +1,88 @@
+import { existsSync, readdirSync, readFileSync } from "node:fs";
+import { join } from "node:path";
+import { describe, expect, test } from "vitest";
+
+const root = process.cwd();
+const componentsDir = join(root, "src/lib/ui/components");
+const storiesDir = join(root, "src/lib/ui/stories");
+
+const requiredStoryFiles = [
+ "Badge.stories.svelte",
+ "Button.stories.svelte",
+ "DashboardFrame.stories.svelte",
+ "DashboardHeader.stories.svelte",
+ "DashboardOnePager.stories.svelte",
+ "FooterCell.stories.svelte",
+ "FooterStatusCell.stories.svelte",
+ "GridFrame.stories.svelte",
+ "IconButton.stories.svelte",
+ "IconGlyph.stories.svelte",
+ "ModuleCard.stories.svelte",
+ "Panel.stories.svelte",
+ "ProgressMeter.stories.svelte",
+ "Separator.stories.svelte",
+ "ServiceGroupPanel.stories.svelte",
+ "ServicePanel.stories.svelte",
+ "ServiceRow.stories.svelte",
+ "Sparkline.stories.svelte",
+ "StatusBadge.stories.svelte",
+ "StatusStrip.stories.svelte",
+ "SystemState.stories.svelte",
+ "TelemetryCard.stories.svelte",
+ "TelemetryGrid.stories.svelte",
+ "TelemetryStrip.stories.svelte",
+ "WeatherModule.stories.svelte",
+] as const;
+
+const forbiddenStoryContent = [
+ "dimension lab",
+ "dimensionlab",
+ "vince",
+ "homepage",
+] as const;
+
+describe("Storybook inventory", () => {
+ test("exposes scripts for local and static Storybook review", () => {
+ const packageJson = JSON.parse(readFileSync(join(root, "package.json"), "utf8")) as {
+ scripts?: Record;
+ };
+
+ expect(packageJson.scripts?.storybook).toBeTypeOf("string");
+ expect(packageJson.scripts?.storybook).toContain("storybook dev");
+ expect(packageJson.scripts?.["build-storybook"]).toBe("storybook build");
+ });
+
+ test("has a story for every reusable dashboard UI component", () => {
+ for (const filename of requiredStoryFiles) {
+ expect(existsSync(join(storiesDir, filename)), `${filename} is missing`).toBe(true);
+ }
+ });
+
+ test("keeps component and story files paired as the UI inventory changes", () => {
+ const componentStoryFiles = readdirSync(componentsDir)
+ .filter((filename) => filename.endsWith(".svelte"))
+ .map((filename) => filename.replace(".svelte", ".stories.svelte"));
+
+ for (const filename of componentStoryFiles) {
+ expect(existsSync(join(storiesDir, filename)), `${filename} is missing`).toBe(true);
+ }
+ });
+
+ test("keeps Storybook fixtures generic and content-free", () => {
+ const storyText = readdirSync(storiesDir)
+ .filter((filename) => filename.endsWith(".svelte") || filename.endsWith(".ts"))
+ .map((filename) => readFileSync(join(storiesDir, filename), "utf8").toLowerCase())
+ .join("\n");
+
+ for (const forbidden of forbiddenStoryContent) {
+ expect(storyText).not.toContain(forbidden);
+ }
+ });
+
+ test("does not add deferred form/navigation primitives", () => {
+ for (const component of ["Input", "ToggleGroup", "ScrollArea"]) {
+ expect(existsSync(join(root, `src/lib/ui/components/${component}.svelte`))).toBe(false);
+ expect(existsSync(join(storiesDir, `${component}.stories.svelte`))).toBe(false);
+ }
+ });
+});
diff --git a/src/lib/ui/tokens.css b/src/lib/ui/tokens.css
new file mode 100644
index 0000000..56c2f7e
--- /dev/null
+++ b/src/lib/ui/tokens.css
@@ -0,0 +1,81 @@
+:root {
+ color-scheme: dark;
+ --ui-color-canvas: #020302;
+ --ui-color-surface: #060706;
+ --ui-color-surface-raised: #0b0d0c;
+ --ui-color-line: rgba(244, 244, 244, 0.16);
+ --ui-color-line-strong: rgba(244, 244, 244, 0.32);
+ --ui-color-text: #f3f4ed;
+ --ui-color-muted: #8d948c;
+ --ui-color-dim: #555b55;
+ --ui-color-accent: #d7ff00;
+ --ui-color-ok: #bfff00;
+ --ui-color-warning: #ffb020;
+ --ui-color-danger: #ff1744;
+ --ui-color-stale: #82909a;
+ --ui-color-unavailable: #65707a;
+ --ui-font-mono: "IBM Plex Mono", "Roboto Mono", "SFMono-Regular", Consolas, monospace;
+ --ui-font-display: "Teko", "IBM Plex Mono", "Roboto Mono", monospace;
+ --ui-space-1: 0.25rem;
+ --ui-space-2: 0.5rem;
+ --ui-space-3: 0.75rem;
+ --ui-space-4: 1rem;
+ --ui-space-5: 1.25rem;
+ --ui-space-6: 1.5rem;
+ --ui-radius-none: 0;
+ --ui-border: 1px solid var(--ui-color-line);
+ --ui-border-strong: 1px solid var(--ui-color-line-strong);
+ --ui-shadow-hard: 0 0 0 1px rgba(215, 255, 0, 0.12) inset;
+ --ui-z-base: 0;
+ --ui-z-panel: 1;
+ --ui-z-overlay: 10;
+ --ui-density-card-min: 9.75rem;
+ --ui-focus-ring: 0 0 0 2px var(--ui-color-canvas), 0 0 0 4px var(--ui-color-accent);
+}
+
+html {
+ background: var(--ui-color-canvas);
+}
+
+body {
+ min-width: 320px;
+ min-height: 100vh;
+ margin: 0;
+ background:
+ linear-gradient(rgba(255, 255, 255, 0.035) 1px, transparent 1px),
+ linear-gradient(90deg, rgba(255, 255, 255, 0.035) 1px, transparent 1px),
+ var(--ui-color-canvas);
+ background-size: 48px 48px, 48px 48px, auto;
+ color: var(--ui-color-text);
+ font-family: var(--ui-font-mono);
+ text-rendering: geometricPrecision;
+}
+
+button,
+input,
+textarea,
+select {
+ font: inherit;
+}
+
+button:focus-visible,
+a:focus-visible,
+[tabindex]:focus-visible {
+ outline: 0;
+ box-shadow: var(--ui-focus-ring);
+}
+
+a {
+ color: inherit;
+}
+
+@media (prefers-reduced-motion: reduce) {
+ *,
+ *::before,
+ *::after {
+ animation-duration: 0.01ms !important;
+ animation-iteration-count: 1 !important;
+ scroll-behavior: auto !important;
+ transition-duration: 0.01ms !important;
+ }
+}
diff --git a/src/lib/ui/types.ts b/src/lib/ui/types.ts
new file mode 100644
index 0000000..55d8aee
--- /dev/null
+++ b/src/lib/ui/types.ts
@@ -0,0 +1,87 @@
+export type UiSeverity =
+ | "neutral"
+ | "ok"
+ | "warning"
+ | "danger"
+ | "stale"
+ | "unavailable"
+ | "loading";
+
+export type UiMetricKind =
+ | "bytes"
+ | "latency"
+ | "number"
+ | "percent"
+ | "temperature"
+ | "text";
+
+export interface UiMetricValue {
+ kind: UiMetricKind;
+ value: number | string;
+ unit?: string;
+ precision?: number;
+}
+
+export interface UiLink {
+ href: string;
+ label?: string;
+ external?: boolean;
+}
+
+export interface UiTelemetryCard {
+ id: string;
+ label: string;
+ value: UiMetricValue;
+ detail?: string;
+ description?: string;
+ icon?: string;
+ severity: UiSeverity;
+ progress?: number;
+ sparkline?: number[];
+}
+
+export interface UiServiceRow {
+ id: string;
+ label: string;
+ description: string;
+ icon?: string;
+ severity: UiSeverity;
+ detail?: string;
+ link?: UiLink;
+}
+
+export interface UiServiceGroup {
+ id: string;
+ title: string;
+ services: UiServiceRow[];
+ summary?: UiStatusItem[];
+}
+
+export interface UiStatusItem {
+ id: string;
+ label: string;
+ value: string;
+ severity?: UiSeverity;
+ link?: UiLink;
+}
+
+export interface UiModuleBlock {
+ id: string;
+ title?: string;
+ label?: string;
+ value?: string;
+ detail?: string;
+ icon?: string;
+ severity?: UiSeverity;
+}
+
+export interface UiDashboardPreview {
+ title: string;
+ subtitle?: string;
+ eyebrow?: string;
+ telemetry: UiTelemetryCard[];
+ serviceGroups: UiServiceGroup[];
+ modules: UiModuleBlock[];
+ statusItems: UiStatusItem[];
+ statusStripId?: string;
+}
diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte
new file mode 100644
index 0000000..801be90
--- /dev/null
+++ b/src/routes/+layout.svelte
@@ -0,0 +1,7 @@
+
+
+{@render children()}
diff --git a/src/routes/+page.server.ts b/src/routes/+page.server.ts
new file mode 100644
index 0000000..c779342
--- /dev/null
+++ b/src/routes/+page.server.ts
@@ -0,0 +1,7 @@
+import { loadDashboardRuntime } from "$lib/server/dashboard";
+
+export function load() {
+ return {
+ dashboard: loadDashboardRuntime(undefined, { seedIfEmpty: true }),
+ };
+}
diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte
new file mode 100644
index 0000000..b4c4f11
--- /dev/null
+++ b/src/routes/+page.svelte
@@ -0,0 +1,97 @@
+
+
+
+ {pageTitle}
+
+
+
+{#if dashboard}
+
+{:else}
+
+
+ {#if stateErrors.length}
+
+ {#each stateErrors as error}
+ {error}
+ {/each}
+
+ {/if}
+
+{/if}
+
+
+
+
diff --git a/src/routes/page.test.ts b/src/routes/page.test.ts
new file mode 100644
index 0000000..c302884
--- /dev/null
+++ b/src/routes/page.test.ts
@@ -0,0 +1,80 @@
+import { render } from "svelte/server";
+import { describe, expect, test } from "vitest";
+import { genericDashboardFixture } from "$lib/model/fixtures/generic";
+import Page from "./+page.svelte";
+
+describe("home page model renderer", () => {
+ test("renders the active dashboard model from the server load", () => {
+ const { body } = render(Page, {
+ props: {
+ data: {
+ dashboard: {
+ state: "ready",
+ document: genericDashboardFixture,
+ schemaVersion: "dashboard.v1",
+ currentRevisionId: "revision-1234567890",
+ },
+ },
+ },
+ });
+
+ expect(body).toContain("Operations Console");
+ expect(body).toContain("Service Uptime");
+ expect(body).toContain("Identity");
+ expect(body).toContain("data-model-id=\"service-uptime\"");
+ expect(body).not.toContain("primary");
+ expect(body).not.toContain("secondary");
+ });
+
+ test("renders invalid model state without crashing", () => {
+ const { body } = render(Page, {
+ props: {
+ data: {
+ dashboard: {
+ state: "invalid",
+ title: "Invalid Dashboard",
+ subtitle: "Validation failed",
+ message: "Dashboard document is invalid.",
+ errors: ["/metadata/title is required"],
+ },
+ },
+ },
+ });
+
+ expect(body).toContain("Invalid Dashboard");
+ expect(body).toContain("Validation failed");
+ expect(body).toContain("Dashboard document is invalid.");
+ });
+
+ test("renders empty and loading model states without crashing", () => {
+ const empty = render(Page, {
+ props: {
+ data: {
+ dashboard: {
+ state: "empty",
+ title: "No Dashboard Model",
+ subtitle: "No active document",
+ message: "No validated dashboard document is active yet.",
+ },
+ },
+ },
+ });
+ const loading = render(Page, {
+ props: {
+ data: {
+ dashboard: {
+ state: "loading",
+ title: "Loading Dashboard",
+ subtitle: "Fetching active model",
+ message: "Waiting for the active dashboard document.",
+ },
+ },
+ },
+ });
+
+ expect(empty.body).toContain("No Dashboard Model");
+ expect(empty.body).toContain("No active document");
+ expect(loading.body).toContain("Loading Dashboard");
+ expect(loading.body).toContain("Fetching active model");
+ });
+});
diff --git a/svelte.config.js b/svelte.config.js
new file mode 100644
index 0000000..bc8b8d4
--- /dev/null
+++ b/svelte.config.js
@@ -0,0 +1,12 @@
+import adapter from "@sveltejs/adapter-node";
+import { vitePreprocess } from "@sveltejs/vite-plugin-svelte";
+
+/** @type {import("@sveltejs/kit").Config} */
+const config = {
+ preprocess: vitePreprocess(),
+ kit: {
+ adapter: adapter(),
+ },
+};
+
+export default config;
diff --git a/tsconfig.base.json b/tsconfig.base.json
deleted file mode 100644
index dcde3db..0000000
--- a/tsconfig.base.json
+++ /dev/null
@@ -1,18 +0,0 @@
-{
- "compilerOptions": {
- "allowJs": true,
- "checkJs": true,
- "esModuleInterop": true,
- "forceConsistentCasingInFileNames": true,
- "ignoreDeprecations": "6.0",
- "jsx": "react-jsx",
- "module": "ESNext",
- "moduleResolution": "bundler",
- "resolveJsonModule": true,
- "skipLibCheck": true,
- "sourceMap": true,
- "strict": true,
- "target": "ES2022",
- "types": ["node", "bun-types", "react", "react-dom"]
- }
-}
diff --git a/tsconfig.json b/tsconfig.json
index 8fe9b00..c08a57f 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -1,8 +1,15 @@
{
- "files": [],
- "references": [
- { "path": "./packages/dashboard-model" },
- { "path": "./apps/web" },
- { "path": "./packages/ui" }
- ]
+ "extends": "./.svelte-kit/tsconfig.json",
+ "compilerOptions": {
+ "allowJs": true,
+ "checkJs": true,
+ "esModuleInterop": true,
+ "forceConsistentCasingInFileNames": true,
+ "resolveJsonModule": true,
+ "skipLibCheck": true,
+ "sourceMap": true,
+ "strict": true,
+ "moduleResolution": "bundler",
+ "types": ["node", "bun-types"]
+ }
}
diff --git a/turbo.json b/turbo.json
deleted file mode 100644
index e1b2e7c..0000000
--- a/turbo.json
+++ /dev/null
@@ -1,98 +0,0 @@
-{
- "$schema": "https://turbo.build/schema.json",
- "globalDependencies": [
- "bun.lock",
- "package.json",
- "tsconfig.base.json",
- "tsconfig.json"
- ],
- "tasks": {
- "build": {
- "dependsOn": ["^build"],
- "inputs": ["$TURBO_DEFAULT$", ".env*"],
- "outputs": ["dist/**", "build/**"],
- "env": ["NODE_ENV", "VITE_*"]
- },
- "check": {
- "dependsOn": ["^build"],
- "outputs": []
- },
- "test:unit": {
- "dependsOn": ["^build"],
- "outputs": [],
- "env": [
- "AGENT_CONFIG_TOKEN",
- "DASHBOARD_MIGRATIONS_DIR",
- "DATABASE_URL",
- "DISABLE_LIVE_DATASOURCES",
- "PROMETHEUS_BASE_URL"
- ]
- },
- "build-storybook": {
- "dependsOn": ["^build"],
- "inputs": ["$TURBO_DEFAULT$", ".env*"],
- "outputs": ["storybook-static/**"],
- "env": ["NODE_ENV", "STORYBOOK_*", "VITE_*"]
- },
- "test:e2e": {
- "dependsOn": [
- "build",
- "^build",
- "@dimensionlab/ui#build-storybook"
- ],
- "outputs": ["test-results/**", "playwright-report/**"],
- "env": [
- "AGENT_CONFIG_TOKEN",
- "CI",
- "DASHBOARD_MIGRATIONS_DIR",
- "DATABASE_URL",
- "DISABLE_LIVE_DATASOURCES",
- "PLAYWRIGHT_DATABASE_URL",
- "PLAYWRIGHT_PORT",
- "PLAYWRIGHT_STORYBOOK_PORT",
- "PROMETHEUS_BASE_URL",
- "STORYBOOK_STATIC_PORT"
- ]
- },
- "db:generate": {
- "cache": false
- },
- "db:check": {
- "outputs": [],
- "env": ["DATABASE_URL"]
- },
- "dev": {
- "dependsOn": ["^build"],
- "cache": false,
- "persistent": true,
- "env": [
- "AGENT_CONFIG_TOKEN",
- "DASHBOARD_DEV_API_HOST",
- "DASHBOARD_DEV_API_PORT",
- "DASHBOARD_DEV_API_TARGET",
- "DASHBOARD_MIGRATIONS_DIR",
- "DATABASE_URL",
- "HOST",
- "PORT",
- "PROMETHEUS_BASE_URL"
- ]
- },
- "preview": {
- "cache": false,
- "persistent": true,
- "env": [
- "AGENT_CONFIG_TOKEN",
- "DASHBOARD_MIGRATIONS_DIR",
- "DATABASE_URL",
- "HOST",
- "PORT",
- "PROMETHEUS_BASE_URL"
- ]
- },
- "storybook": {
- "cache": false,
- "persistent": true,
- "env": ["HOST", "PORT", "STORYBOOK_*", "VITE_*"]
- }
- }
-}
diff --git a/vite.config.ts b/vite.config.ts
new file mode 100644
index 0000000..80864b9
--- /dev/null
+++ b/vite.config.ts
@@ -0,0 +1,6 @@
+import { sveltekit } from "@sveltejs/kit/vite";
+import { defineConfig } from "vite";
+
+export default defineConfig({
+ plugins: [sveltekit()],
+});