img:first-child]:pt-0 data-[size=sm]:[--card-spacing:--spacing(3)] data-[size=sm]:has-data-[slot=card-footer]:pb-0 *:[img:first-child]:rounded-t-xl *:[img:last-child]:rounded-b-xl",
- className
- )}
- {...props}
- />
- )
-}
-
-function CardHeader({ className, ...props }: React.ComponentProps<"div">) {
- return (
-
- )
-}
-
-function CardTitle({ className, ...props }: React.ComponentProps<"div">) {
- return (
-
- )
-}
-
-function CardDescription({ className, ...props }: React.ComponentProps<"div">) {
- return (
-
- )
-}
-
-function CardAction({ className, ...props }: React.ComponentProps<"div">) {
- return (
-
- )
-}
-
-function CardContent({ className, ...props }: React.ComponentProps<"div">) {
- return (
-
- )
-}
-
-function CardFooter({ className, ...props }: React.ComponentProps<"div">) {
- return (
-
- )
-}
-
-export {
- Card,
- CardHeader,
- CardFooter,
- CardTitle,
- CardAction,
- CardDescription,
- CardContent,
-}
diff --git a/packages/ui/src/primitives/progress.tsx b/packages/ui/src/primitives/progress.tsx
deleted file mode 100644
index 59fac6c..0000000
--- a/packages/ui/src/primitives/progress.tsx
+++ /dev/null
@@ -1,29 +0,0 @@
-import * as React from "react"
-import { Progress as ProgressPrimitive } from "radix-ui"
-
-import { cn } from "../utils"
-
-function Progress({
- className,
- value,
- ...props
-}: React.ComponentProps
) {
- return (
-
-
-
- )
-}
-
-export { Progress }
diff --git a/packages/ui/src/primitives/separator.tsx b/packages/ui/src/primitives/separator.tsx
deleted file mode 100644
index ee9f6cb..0000000
--- a/packages/ui/src/primitives/separator.tsx
+++ /dev/null
@@ -1,28 +0,0 @@
-"use client"
-
-import * as React from "react"
-import { Separator as SeparatorPrimitive } from "radix-ui"
-
-import { cn } from "../utils"
-
-function Separator({
- className,
- orientation = "horizontal",
- decorative = true,
- ...props
-}: React.ComponentProps) {
- return (
-
- )
-}
-
-export { Separator }
diff --git a/packages/ui/src/primitives/skeleton.tsx b/packages/ui/src/primitives/skeleton.tsx
deleted file mode 100644
index a1df193..0000000
--- a/packages/ui/src/primitives/skeleton.tsx
+++ /dev/null
@@ -1,13 +0,0 @@
-import { cn } from "../utils"
-
-function Skeleton({ className, ...props }: React.ComponentProps<"div">) {
- return (
-
- )
-}
-
-export { Skeleton }
diff --git a/packages/ui/src/stories/Badge.stories.tsx b/packages/ui/src/stories/Badge.stories.tsx
deleted file mode 100644
index 26e3ea8..0000000
--- a/packages/ui/src/stories/Badge.stories.tsx
+++ /dev/null
@@ -1,16 +0,0 @@
-import type { Meta, StoryObj } from "@storybook/react-vite";
-import { Badge } from "../index";
-
-const meta = {
- title: "UI/Badge",
- component: Badge,
- args: {
- label: "Ready",
- severity: "ok",
- },
-} satisfies Meta;
-
-export default meta;
-type Story = StoryObj;
-
-export const Default: Story = {};
diff --git a/packages/ui/src/stories/Button.stories.tsx b/packages/ui/src/stories/Button.stories.tsx
deleted file mode 100644
index 084caa8..0000000
--- a/packages/ui/src/stories/Button.stories.tsx
+++ /dev/null
@@ -1,21 +0,0 @@
-import type { Meta, StoryObj } from "@storybook/react-vite";
-import { Button } from "../index";
-
-const meta = {
- title: "UI/Button",
- component: Button,
- args: {
- label: "Refresh",
- icon: "mdi:refresh",
- },
-} satisfies Meta;
-
-export default meta;
-type Story = StoryObj;
-
-export const Primary: Story = {};
-export const Secondary: Story = {
- args: {
- variant: "secondary",
- },
-};
diff --git a/packages/ui/src/stories/CornerBracketFrame.stories.tsx b/packages/ui/src/stories/CornerBracketFrame.stories.tsx
deleted file mode 100644
index d5c0110..0000000
--- a/packages/ui/src/stories/CornerBracketFrame.stories.tsx
+++ /dev/null
@@ -1,16 +0,0 @@
-import type { Meta, StoryObj } from "@storybook/react-vite";
-import { CornerBracketFrame } from "../index";
-
-const meta = {
- title: "UI/CornerBracketFrame",
- component: CornerBracketFrame,
- args: {
- tone: "accent",
- size: "md",
- },
-} satisfies Meta;
-
-export default meta;
-type Story = StoryObj;
-
-export const Default: Story = {};
diff --git a/packages/ui/src/stories/DashboardFrame.stories.tsx b/packages/ui/src/stories/DashboardFrame.stories.tsx
deleted file mode 100644
index 894219e..0000000
--- a/packages/ui/src/stories/DashboardFrame.stories.tsx
+++ /dev/null
@@ -1,21 +0,0 @@
-import type { Meta, StoryObj } from "@storybook/react-vite";
-import { DashboardFrame } from "../index";
-import { fullCompositionDashboard, secondaryDashboard } from "./story-data";
-
-const meta = {
- title: "UI/DashboardFrame",
- component: DashboardFrame,
- args: {
- dashboard: fullCompositionDashboard,
- },
-} satisfies Meta;
-
-export default meta;
-type Story = StoryObj;
-
-export const FullComposition: Story = {};
-export const Secondary: Story = {
- args: {
- dashboard: secondaryDashboard,
- },
-};
diff --git a/packages/ui/src/stories/DashboardHeader.stories.tsx b/packages/ui/src/stories/DashboardHeader.stories.tsx
deleted file mode 100644
index c337158..0000000
--- a/packages/ui/src/stories/DashboardHeader.stories.tsx
+++ /dev/null
@@ -1,19 +0,0 @@
-import type { Meta, StoryObj } from "@storybook/react-vite";
-import { DashboardHeader } from "../index";
-import { moduleBlocks } from "./story-data";
-
-const meta = {
- title: "UI/DashboardHeader",
- component: DashboardHeader,
- args: {
- eyebrow: "Preview Surface",
- title: "Operations Console",
- subtitle: "Generic dashboard header",
- module: moduleBlocks.compact,
- },
-} satisfies Meta;
-
-export default meta;
-type Story = StoryObj;
-
-export const WithModule: Story = {};
diff --git a/packages/ui/src/stories/DashboardOnePager.stories.tsx b/packages/ui/src/stories/DashboardOnePager.stories.tsx
deleted file mode 100644
index 3bd800f..0000000
--- a/packages/ui/src/stories/DashboardOnePager.stories.tsx
+++ /dev/null
@@ -1,16 +0,0 @@
-import type { Meta, StoryObj } from "@storybook/react-vite";
-import { DashboardFrame } from "../index";
-import { fullCompositionDashboard } from "./story-data";
-
-const meta = {
- title: "UI/DashboardOnePager",
- component: DashboardFrame,
- args: {
- dashboard: fullCompositionDashboard,
- },
-} satisfies Meta;
-
-export default meta;
-type Story = StoryObj;
-
-export const OnePager: Story = {};
diff --git a/packages/ui/src/stories/DiagonalStripeField.stories.tsx b/packages/ui/src/stories/DiagonalStripeField.stories.tsx
deleted file mode 100644
index 1f094ee..0000000
--- a/packages/ui/src/stories/DiagonalStripeField.stories.tsx
+++ /dev/null
@@ -1,16 +0,0 @@
-import type { Meta, StoryObj } from "@storybook/react-vite";
-import { DiagonalStripeField } from "../index";
-
-const meta = {
- title: "UI/DiagonalStripeField",
- component: DiagonalStripeField,
- args: {
- tone: "accent",
- density: "regular",
- },
-} satisfies Meta;
-
-export default meta;
-type Story = StoryObj;
-
-export const Default: Story = {};
diff --git a/packages/ui/src/stories/FooterCell.stories.tsx b/packages/ui/src/stories/FooterCell.stories.tsx
deleted file mode 100644
index 7aa06d8..0000000
--- a/packages/ui/src/stories/FooterCell.stories.tsx
+++ /dev/null
@@ -1,21 +0,0 @@
-import type { Meta, StoryObj } from "@storybook/react-vite";
-import { FooterCell } from "../index";
-import { statusItems } from "./story-data";
-
-const meta = {
- title: "UI/FooterCell",
- component: FooterCell,
- args: {
- item: statusItems.ok,
- },
-} satisfies Meta;
-
-export default meta;
-type Story = StoryObj;
-
-export const Operational: Story = {};
-export const Linked: Story = {
- args: {
- item: statusItems.action,
- },
-};
diff --git a/packages/ui/src/stories/FooterStatusCell.stories.tsx b/packages/ui/src/stories/FooterStatusCell.stories.tsx
deleted file mode 100644
index bd985a1..0000000
--- a/packages/ui/src/stories/FooterStatusCell.stories.tsx
+++ /dev/null
@@ -1,16 +0,0 @@
-import type { Meta, StoryObj } from "@storybook/react-vite";
-import { FooterStatusCell } from "../index";
-import { statusItems } from "./story-data";
-
-const meta = {
- title: "UI/FooterStatusCell",
- component: FooterStatusCell,
- args: {
- item: statusItems.degraded,
- },
-} satisfies Meta;
-
-export default meta;
-type Story = StoryObj;
-
-export const Warning: Story = {};
diff --git a/packages/ui/src/stories/GridFrame.stories.tsx b/packages/ui/src/stories/GridFrame.stories.tsx
deleted file mode 100644
index 191bf6c..0000000
--- a/packages/ui/src/stories/GridFrame.stories.tsx
+++ /dev/null
@@ -1,24 +0,0 @@
-import type { Meta, StoryObj } from "@storybook/react-vite";
-import { GridFrame, ModuleCard } from "../index";
-import { moduleBlocks } from "./story-data";
-
-const meta = {
- title: "UI/GridFrame",
- component: GridFrame,
- render: (args) => (
-
-
-
-
-
- ),
-} satisfies Meta;
-
-export default meta;
-type Story = StoryObj;
-
-export const Dense: Story = {
- args: {
- density: "dense",
- },
-};
diff --git a/packages/ui/src/stories/IconButton.stories.tsx b/packages/ui/src/stories/IconButton.stories.tsx
deleted file mode 100644
index 612bf2a..0000000
--- a/packages/ui/src/stories/IconButton.stories.tsx
+++ /dev/null
@@ -1,16 +0,0 @@
-import type { Meta, StoryObj } from "@storybook/react-vite";
-import { IconButton } from "../index";
-
-const meta = {
- title: "UI/IconButton",
- component: IconButton,
- args: {
- icon: "mdi:refresh",
- label: "Refresh status",
- },
-} satisfies Meta;
-
-export default meta;
-type Story = StoryObj;
-
-export const Default: Story = {};
diff --git a/packages/ui/src/stories/IconGlyph.stories.tsx b/packages/ui/src/stories/IconGlyph.stories.tsx
deleted file mode 100644
index d832951..0000000
--- a/packages/ui/src/stories/IconGlyph.stories.tsx
+++ /dev/null
@@ -1,17 +0,0 @@
-import type { Meta, StoryObj } from "@storybook/react-vite";
-import { IconGlyph } from "../index";
-
-const meta = {
- title: "UI/IconGlyph",
- component: IconGlyph,
- args: {
- name: "mdi:server-network",
- label: "Generic service",
- size: "md",
- },
-} satisfies Meta;
-
-export default meta;
-type Story = StoryObj;
-
-export const Default: Story = {};
diff --git a/packages/ui/src/stories/LineChart.stories.tsx b/packages/ui/src/stories/LineChart.stories.tsx
deleted file mode 100644
index ccbd502..0000000
--- a/packages/ui/src/stories/LineChart.stories.tsx
+++ /dev/null
@@ -1,17 +0,0 @@
-import type { Meta, StoryObj } from "@storybook/react-vite";
-import { LineChart } from "../index";
-
-const meta = {
- title: "UI/LineChart",
- component: LineChart,
- args: {
- values: [12, 18, 24, 20, 36, 44],
- severity: "ok",
- label: "Generic trend",
- },
-} satisfies Meta;
-
-export default meta;
-type Story = StoryObj;
-
-export const Trend: Story = {};
diff --git a/packages/ui/src/stories/ModuleCard.stories.tsx b/packages/ui/src/stories/ModuleCard.stories.tsx
deleted file mode 100644
index ea0305b..0000000
--- a/packages/ui/src/stories/ModuleCard.stories.tsx
+++ /dev/null
@@ -1,21 +0,0 @@
-import type { Meta, StoryObj } from "@storybook/react-vite";
-import { ModuleCard } from "../index";
-import { moduleBlocks } from "./story-data";
-
-const meta = {
- title: "UI/ModuleCard",
- component: ModuleCard,
- args: {
- module: moduleBlocks.compact,
- },
-} satisfies Meta;
-
-export default meta;
-type Story = StoryObj;
-
-export const Compact: Story = {};
-export const Long: Story = {
- args: {
- module: moduleBlocks.long,
- },
-};
diff --git a/packages/ui/src/stories/Panel.stories.tsx b/packages/ui/src/stories/Panel.stories.tsx
deleted file mode 100644
index 91eb61e..0000000
--- a/packages/ui/src/stories/Panel.stories.tsx
+++ /dev/null
@@ -1,22 +0,0 @@
-import type { Meta, StoryObj } from "@storybook/react-vite";
-import { Panel, ServiceRow } from "../index";
-import { serviceRows } from "./story-data";
-
-const meta = {
- title: "UI/Panel",
- component: Panel,
- render: (args) => (
-
-
-
-
- ),
- args: {
- title: "Generic Panel",
- },
-} satisfies Meta;
-
-export default meta;
-type Story = StoryObj;
-
-export const Dense: Story = {};
diff --git a/packages/ui/src/stories/ProgressMeter.stories.tsx b/packages/ui/src/stories/ProgressMeter.stories.tsx
deleted file mode 100644
index eec35c2..0000000
--- a/packages/ui/src/stories/ProgressMeter.stories.tsx
+++ /dev/null
@@ -1,17 +0,0 @@
-import type { Meta, StoryObj } from "@storybook/react-vite";
-import { ProgressMeter } from "../index";
-
-const meta = {
- title: "UI/ProgressMeter",
- component: ProgressMeter,
- args: {
- value: 72,
- severity: "ok",
- label: "Capacity",
- },
-} satisfies Meta;
-
-export default meta;
-type Story = StoryObj;
-
-export const Filled: Story = {};
diff --git a/packages/ui/src/stories/ScanlineField.stories.tsx b/packages/ui/src/stories/ScanlineField.stories.tsx
deleted file mode 100644
index fbe53f4..0000000
--- a/packages/ui/src/stories/ScanlineField.stories.tsx
+++ /dev/null
@@ -1,16 +0,0 @@
-import type { Meta, StoryObj } from "@storybook/react-vite";
-import { ScanlineField } from "../index";
-
-const meta = {
- title: "UI/ScanlineField",
- component: ScanlineField,
- args: {
- tone: "accent",
- intensity: "medium",
- },
-} satisfies Meta;
-
-export default meta;
-type Story = StoryObj;
-
-export const Default: Story = {};
diff --git a/packages/ui/src/stories/Separator.stories.tsx b/packages/ui/src/stories/Separator.stories.tsx
deleted file mode 100644
index 986ed23..0000000
--- a/packages/ui/src/stories/Separator.stories.tsx
+++ /dev/null
@@ -1,15 +0,0 @@
-import type { Meta, StoryObj } from "@storybook/react-vite";
-import { Separator } from "../index";
-
-const meta = {
- title: "UI/Separator",
- component: Separator,
- args: {
- orientation: "horizontal",
- },
-} satisfies Meta;
-
-export default meta;
-type Story = StoryObj;
-
-export const Horizontal: Story = {};
diff --git a/packages/ui/src/stories/ServiceGroupPanel.stories.tsx b/packages/ui/src/stories/ServiceGroupPanel.stories.tsx
deleted file mode 100644
index f5ebdce..0000000
--- a/packages/ui/src/stories/ServiceGroupPanel.stories.tsx
+++ /dev/null
@@ -1,16 +0,0 @@
-import type { Meta, StoryObj } from "@storybook/react-vite";
-import { ServiceGroupPanel } from "../index";
-import { serviceGroups } from "./story-data";
-
-const meta = {
- title: "UI/ServiceGroupPanel",
- component: ServiceGroupPanel,
- args: {
- group: serviceGroups.mixed,
- },
-} satisfies Meta;
-
-export default meta;
-type Story = StoryObj;
-
-export const Mixed: Story = {};
diff --git a/packages/ui/src/stories/ServicePanel.stories.tsx b/packages/ui/src/stories/ServicePanel.stories.tsx
deleted file mode 100644
index d29b7ad..0000000
--- a/packages/ui/src/stories/ServicePanel.stories.tsx
+++ /dev/null
@@ -1,16 +0,0 @@
-import type { Meta, StoryObj } from "@storybook/react-vite";
-import { ServicePanel } from "../index";
-import { serviceGroups } from "./story-data";
-
-const meta = {
- title: "UI/ServicePanel",
- component: ServicePanel,
- args: {
- group: serviceGroups.long,
- },
-} satisfies Meta;
-
-export default meta;
-type Story = StoryObj;
-
-export const LongList: Story = {};
diff --git a/packages/ui/src/stories/ServiceRow.stories.tsx b/packages/ui/src/stories/ServiceRow.stories.tsx
deleted file mode 100644
index 56ba076..0000000
--- a/packages/ui/src/stories/ServiceRow.stories.tsx
+++ /dev/null
@@ -1,21 +0,0 @@
-import type { Meta, StoryObj } from "@storybook/react-vite";
-import { ServiceRow } from "../index";
-import { serviceRows } from "./story-data";
-
-const meta = {
- title: "UI/ServiceRow",
- component: ServiceRow,
- args: {
- service: serviceRows.normal,
- },
-} satisfies Meta;
-
-export default meta;
-type Story = StoryObj;
-
-export const Normal: Story = {};
-export const Warning: Story = {
- args: {
- service: serviceRows.degraded,
- },
-};
diff --git a/packages/ui/src/stories/SignalTrace.stories.tsx b/packages/ui/src/stories/SignalTrace.stories.tsx
deleted file mode 100644
index 4089df8..0000000
--- a/packages/ui/src/stories/SignalTrace.stories.tsx
+++ /dev/null
@@ -1,16 +0,0 @@
-import type { Meta, StoryObj } from "@storybook/react-vite";
-import { SignalTrace } from "../index";
-
-const meta = {
- title: "UI/SignalTrace",
- component: SignalTrace,
- args: {
- tone: "accent",
- orientation: "horizontal",
- },
-} satisfies Meta;
-
-export default meta;
-type Story = StoryObj;
-
-export const Horizontal: Story = {};
diff --git a/packages/ui/src/stories/Sparkline.stories.tsx b/packages/ui/src/stories/Sparkline.stories.tsx
deleted file mode 100644
index af3e6e5..0000000
--- a/packages/ui/src/stories/Sparkline.stories.tsx
+++ /dev/null
@@ -1,16 +0,0 @@
-import type { Meta, StoryObj } from "@storybook/react-vite";
-import { Sparkline } from "../index";
-
-const meta = {
- title: "UI/Sparkline",
- component: Sparkline,
- args: {
- values: [8, 14, 12, 24, 18, 30],
- severity: "ok",
- },
-} satisfies Meta;
-
-export default meta;
-type Story = StoryObj;
-
-export const Default: Story = {};
diff --git a/packages/ui/src/stories/StatusBadge.stories.tsx b/packages/ui/src/stories/StatusBadge.stories.tsx
deleted file mode 100644
index 92a444d..0000000
--- a/packages/ui/src/stories/StatusBadge.stories.tsx
+++ /dev/null
@@ -1,16 +0,0 @@
-import type { Meta, StoryObj } from "@storybook/react-vite";
-import { StatusBadge } from "../index";
-
-const meta = {
- title: "UI/StatusBadge",
- component: StatusBadge,
- args: {
- label: "Ready",
- severity: "ok",
- },
-} satisfies Meta;
-
-export default meta;
-type Story = StoryObj;
-
-export const Ready: Story = {};
diff --git a/packages/ui/src/stories/StatusStrip.stories.tsx b/packages/ui/src/stories/StatusStrip.stories.tsx
deleted file mode 100644
index 2fc236d..0000000
--- a/packages/ui/src/stories/StatusStrip.stories.tsx
+++ /dev/null
@@ -1,17 +0,0 @@
-import type { Meta, StoryObj } from "@storybook/react-vite";
-import { StatusStrip } from "../index";
-import { mixedStatusStrip } from "./story-data";
-
-const meta = {
- title: "UI/StatusStrip",
- component: StatusStrip,
- args: {
- id: "generic-status",
- items: mixedStatusStrip,
- },
-} satisfies Meta;
-
-export default meta;
-type Story = StoryObj;
-
-export const Mixed: Story = {};
diff --git a/packages/ui/src/stories/SystemState.stories.tsx b/packages/ui/src/stories/SystemState.stories.tsx
deleted file mode 100644
index 9b3f6e5..0000000
--- a/packages/ui/src/stories/SystemState.stories.tsx
+++ /dev/null
@@ -1,18 +0,0 @@
-import type { Meta, StoryObj } from "@storybook/react-vite";
-import { SystemState } from "../index";
-
-const meta = {
- title: "UI/SystemState",
- component: SystemState,
- args: {
- title: "Loading Dashboard",
- detail: "Fetching active model",
- severity: "loading",
- icon: "mdi:progress-clock",
- },
-} satisfies Meta;
-
-export default meta;
-type Story = StoryObj;
-
-export const Loading: Story = {};
diff --git a/packages/ui/src/stories/TelemetryCard.stories.tsx b/packages/ui/src/stories/TelemetryCard.stories.tsx
deleted file mode 100644
index 3ac0e1e..0000000
--- a/packages/ui/src/stories/TelemetryCard.stories.tsx
+++ /dev/null
@@ -1,21 +0,0 @@
-import type { Meta, StoryObj } from "@storybook/react-vite";
-import { TelemetryCard } from "../index";
-import { telemetryCards } from "./story-data";
-
-const meta = {
- title: "UI/TelemetryCard",
- component: TelemetryCard,
- args: {
- card: telemetryCards.percent,
- },
-} satisfies Meta;
-
-export default meta;
-type Story = StoryObj;
-
-export const Percent: Story = {};
-export const Danger: Story = {
- args: {
- card: telemetryCards.danger,
- },
-};
diff --git a/packages/ui/src/stories/TelemetryGrid.stories.tsx b/packages/ui/src/stories/TelemetryGrid.stories.tsx
deleted file mode 100644
index 34493c5..0000000
--- a/packages/ui/src/stories/TelemetryGrid.stories.tsx
+++ /dev/null
@@ -1,21 +0,0 @@
-import type { Meta, StoryObj } from "@storybook/react-vite";
-import { TelemetryGrid } from "../index";
-import { eightTelemetryCards, sixteenTelemetryCards } from "./story-data";
-
-const meta = {
- title: "UI/TelemetryGrid",
- component: TelemetryGrid,
- args: {
- cards: eightTelemetryCards,
- },
-} satisfies Meta;
-
-export default meta;
-type Story = StoryObj;
-
-export const EightCards: Story = {};
-export const SixteenCards: Story = {
- args: {
- cards: sixteenTelemetryCards,
- },
-};
diff --git a/packages/ui/src/stories/TelemetryStrip.stories.tsx b/packages/ui/src/stories/TelemetryStrip.stories.tsx
deleted file mode 100644
index f7c9d13..0000000
--- a/packages/ui/src/stories/TelemetryStrip.stories.tsx
+++ /dev/null
@@ -1,16 +0,0 @@
-import type { Meta, StoryObj } from "@storybook/react-vite";
-import { TelemetryStrip } from "../index";
-import { eightTelemetryCards } from "./story-data";
-
-const meta = {
- title: "UI/TelemetryStrip",
- component: TelemetryStrip,
- args: {
- cards: eightTelemetryCards,
- },
-} satisfies Meta;
-
-export default meta;
-type Story = StoryObj;
-
-export const Default: Story = {};
diff --git a/packages/ui/src/stories/ThemeToggle.stories.tsx b/packages/ui/src/stories/ThemeToggle.stories.tsx
deleted file mode 100644
index f2edeea..0000000
--- a/packages/ui/src/stories/ThemeToggle.stories.tsx
+++ /dev/null
@@ -1,26 +0,0 @@
-import type { Meta, StoryObj } from "@storybook/react-vite";
-import { ThemeToggle } from "../index";
-
-const meta = {
- title: "UI/ThemeToggle",
- component: ThemeToggle,
- args: {
- onThemeChange: () => undefined,
- theme: "dark",
- },
-} satisfies Meta;
-
-export default meta;
-
-type Story = StoryObj;
-
-export const Dark: Story = {};
-
-export const Light: Story = {
- args: {
- theme: "light",
- },
- globals: {
- theme: "light",
- },
-};
diff --git a/packages/ui/src/stories/WeatherModule.stories.tsx b/packages/ui/src/stories/WeatherModule.stories.tsx
deleted file mode 100644
index a376448..0000000
--- a/packages/ui/src/stories/WeatherModule.stories.tsx
+++ /dev/null
@@ -1,16 +0,0 @@
-import type { Meta, StoryObj } from "@storybook/react-vite";
-import { WeatherModule } from "../index";
-import { moduleBlocks } from "./story-data";
-
-const meta = {
- title: "UI/WeatherModule",
- component: WeatherModule,
- args: {
- module: moduleBlocks.compact,
- },
-} satisfies Meta;
-
-export default meta;
-type Story = StoryObj;
-
-export const Default: Story = {};
diff --git a/packages/ui/src/stories/story-data.ts b/packages/ui/src/stories/story-data.ts
deleted file mode 100644
index 4085dac..0000000
--- a/packages/ui/src/stories/story-data.ts
+++ /dev/null
@@ -1,258 +0,0 @@
-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/packages/ui/src/storybook.test.ts b/packages/ui/src/storybook.test.ts
deleted file mode 100644
index 9a18ecd..0000000
--- a/packages/ui/src/storybook.test.ts
+++ /dev/null
@@ -1,309 +0,0 @@
-import { existsSync, readdirSync, readFileSync } from "node:fs";
-import { basename, join, relative } from "node:path";
-import { describe, expect, test } from "vitest";
-
-const root = process.cwd();
-const packageRoot = existsSync(join(root, "packages/ui/package.json"))
- ? join(root, "packages/ui")
- : root;
-const componentsDir = join(packageRoot, "src/components");
-const storiesDir = join(packageRoot, "src/stories");
-
-const allowedComponentDomains = [
- "foundation",
- "frames",
- "operations",
- "telemetry",
-] as const;
-const compositionStoryFiles = ["DashboardOnePager.stories.tsx"] 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(packageRoot, "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 component of componentInventory()) {
- expect(
- existsSync(join(storiesDir, component.storyFile)),
- `${component.storyFile} is missing`,
- ).toBe(true);
- }
- });
-
- test("uses only approved component domain folders", () => {
- const actualDomains = readdirSync(componentsDir, { withFileTypes: true })
- .filter((entry) => entry.isDirectory())
- .map((entry) => entry.name)
- .sort();
-
- expect(actualDomains).toEqual([...allowedComponentDomains].sort());
- });
-
- test("loads dashboard component styles through the global app stylesheet", () => {
- const packageStyles = readFileSync(join(packageRoot, "src/styles.css"), "utf8");
- const dashboardFrame = readFileSync(requiredComponentPath("DashboardFrame"), "utf8");
-
- expect(packageStyles).toContain('./components/styles.css');
- expect(dashboardFrame).not.toContain('./styles.css');
- });
-
- test("configures Storybook theme switching for reusable components", () => {
- const previewSource = readFileSync(join(packageRoot, ".storybook/preview.ts"), "utf8");
-
- expect(previewSource).toContain("globalTypes");
- expect(previewSource).toContain("data-ui-theme");
- expect(previewSource).toContain("../src/styles.css");
- });
-
- test("keeps component and story files paired as the UI inventory changes", () => {
- const componentStoryFiles = new Set(
- componentInventory().map((component) => component.storyFile),
- );
-
- for (const filename of componentStoryFiles) {
- expect(existsSync(join(storiesDir, filename)), `${filename} is missing`).toBe(
- true,
- );
- }
-
- const unpairedStoryFiles = storyFiles().filter(
- (filename) =>
- !componentStoryFiles.has(filename) &&
- !compositionStoryFiles.includes(
- filename as (typeof compositionStoryFiles)[number],
- ),
- );
- expect(unpairedStoryFiles).toEqual([]);
- });
-
- test("exports every reusable component through the package barrel", () => {
- const indexSource = readFileSync(join(packageRoot, "src/index.ts"), "utf8");
-
- for (const component of componentInventory()) {
- expect(indexSource).toContain(
- `export { ${component.name} } from "${component.relativeExportPath}";`,
- );
- }
- });
-
- test("exposes grouped package entrypoints for each component domain", () => {
- const packageJson = JSON.parse(readFileSync(join(packageRoot, "package.json"), "utf8")) as {
- exports?: Record;
- };
- const rootIndexSource = readFileSync(join(packageRoot, "src/index.ts"), "utf8");
- const componentsByDomain = new Map<(typeof allowedComponentDomains)[number], string[]>();
-
- for (const domain of allowedComponentDomains) {
- componentsByDomain.set(domain, []);
- }
-
- for (const component of componentInventory()) {
- componentsByDomain.get(component.domain)?.push(component.name);
- }
-
- for (const domain of allowedComponentDomains) {
- const domainIndexPath = join(componentsDir, domain, "index.ts");
- const domainIndexSource = existsSync(domainIndexPath)
- ? readFileSync(domainIndexPath, "utf8")
- : "";
-
- expect(existsSync(domainIndexPath), `${domain} index is missing`).toBe(true);
- expect(rootIndexSource).toContain(`export * from "./components/${domain}";`);
- expect(packageJson.exports?.[`./${domain}`]).toMatchObject({
- types: `./dist/components/${domain}/index.d.ts`,
- development: `./src/components/${domain}/index.ts`,
- default: `./dist/components/${domain}/index.js`,
- });
-
- for (const componentName of componentsByDomain.get(domain) ?? []) {
- expect(domainIndexSource).toContain(
- `export * from "./${componentName}";`,
- );
- }
- }
- });
-
- test("exports component prop interfaces through grouped package entrypoints", () => {
- let propInterfaceCount = 0;
-
- for (const component of componentInventory()) {
- const source = readFileSync(component.path, "utf8");
- const propTypeName = `${component.name}Props`;
- const propExportPattern = new RegExp(
- `export\\s+(interface|type)\\s+${propTypeName}\\b`,
- );
-
- if (!propExportPattern.test(source)) {
- continue;
- }
-
- propInterfaceCount += 1;
- const domainIndexSource = readFileSync(
- join(componentsDir, component.domain, "index.ts"),
- "utf8",
- );
-
- expect(domainIndexSource).toContain(
- `export * from "./${component.name}";`,
- );
- }
-
- expect(propInterfaceCount).toBeGreaterThan(0);
- });
-
- test("keeps Storybook fixtures generic and content-free", () => {
- const storyText = readdirSync(storiesDir)
- .filter((filename) => filename.endsWith(".tsx") || filename.endsWith(".ts"))
- .map((filename) => readFileSync(join(storiesDir, filename), "utf8").toLowerCase())
- .join("\n");
-
- for (const forbidden of forbiddenStoryContent) {
- expect(storyText).not.toContain(forbidden);
- }
- });
-
- test("includes style-only decorative primitives in the reusable UI inventory", () => {
- for (const component of [
- "CornerBracketFrame",
- "DiagonalStripeField",
- "ScanlineField",
- "SignalTrace",
- ]) {
- expect(existsSync(requiredComponentPath(component)), `${component} is missing`).toBe(true);
- expect(
- existsSync(join(storiesDir, `${component}.stories.tsx`)),
- `${component}.stories.tsx is missing`,
- ).toBe(true);
- }
- });
-
- test("keeps decorative primitives hidden from assistive technology", () => {
- for (const component of [
- "CornerBracketFrame",
- "DiagonalStripeField",
- "ScanlineField",
- "SignalTrace",
- ]) {
- const source = readFileSync(requiredComponentPath(component), "utf8");
-
- expect(source).toContain('aria-hidden="true"');
- }
- });
-
- test("does not add deferred form/navigation primitives", () => {
- const componentNames = new Set(
- componentInventory().map((component) => component.name),
- );
-
- for (const component of ["Input", "ToggleGroup", "ScrollArea"]) {
- expect(componentNames.has(component)).toBe(false);
- expect(existsSync(join(componentsDir, `${component}.tsx`))).toBe(false);
- expect(existsSync(join(storiesDir, `${component}.stories.tsx`))).toBe(false);
- }
- });
-
- test("does not keep legacy stories in the React Storybook inventory", () => {
- const legacyStoryExtension = [".stories", ".sve", "lte"].join("");
- const legacyStories = readdirSync(storiesDir).filter((filename) =>
- filename.endsWith(legacyStoryExtension),
- );
-
- expect(legacyStories).toEqual([]);
- });
-});
-
-function requiredComponentPath(component: string): string {
- const componentPath = componentInventory().find(
- (entry) => entry.name === component,
- )?.path;
-
- expect(componentPath, `${component} is missing from the UI component tree`).toBeTypeOf(
- "string",
- );
-
- return componentPath as string;
-}
-
-interface ComponentInventoryItem {
- domain: (typeof allowedComponentDomains)[number];
- name: string;
- path: string;
- relativeExportPath: string;
- storyFile: string;
-}
-
-type WorkspacePackageExport =
- | string
- | {
- types?: string;
- development?: string;
- default?: string;
- };
-
-function componentInventory(): ComponentInventoryItem[] {
- const components = allowedComponentDomains
- .flatMap((domain) => collectComponentFiles(join(componentsDir, domain)))
- .sort((a, b) => a.name.localeCompare(b.name));
- const names = components.map((component) => component.name);
-
- expect(names).toEqual([...new Set(names)]);
-
- return components;
-}
-
-function collectComponentFiles(directory: string): ComponentInventoryItem[] {
- return readdirSync(directory, { withFileTypes: true }).flatMap((entry) => {
- const entryPath = join(directory, entry.name);
-
- if (entry.isDirectory()) {
- return collectComponentFiles(entryPath);
- }
-
- if (
- !entry.isFile() ||
- !entry.name.endsWith(".tsx") ||
- entry.name.endsWith(".test.tsx")
- ) {
- return [];
- }
-
- const name = basename(entry.name, ".tsx");
- const relativeComponentPath = relative(componentsDir, entryPath).replace(/\\/g, "/");
- const domain = relativeComponentPath.split(
- "/",
- )[0] as (typeof allowedComponentDomains)[number];
- const relativeExportPath = `./${relative(join(packageRoot, "src"), entryPath)
- .replace(/\\/g, "/")
- .replace(/\.tsx$/, "")}`;
-
- return [
- {
- domain,
- name,
- path: entryPath,
- relativeExportPath,
- storyFile: `${name}.stories.tsx`,
- },
- ];
- });
-}
-
-function storyFiles(): string[] {
- return readdirSync(storiesDir)
- .filter((filename) => filename.endsWith(".stories.tsx"))
- .sort();
-}
diff --git a/packages/ui/src/styles.css b/packages/ui/src/styles.css
deleted file mode 100644
index fd6f4a2..0000000
--- a/packages/ui/src/styles.css
+++ /dev/null
@@ -1,4 +0,0 @@
-@import "@fontsource-variable/geist";
-@import "uplot/dist/uPlot.min.css";
-@import "./tokens.css";
-@import "./components/styles.css";
diff --git a/packages/ui/src/theme.test.ts b/packages/ui/src/theme.test.ts
deleted file mode 100644
index 8ed3f04..0000000
--- a/packages/ui/src/theme.test.ts
+++ /dev/null
@@ -1,56 +0,0 @@
-import { describe, expect, test } from "vitest";
-import {
- getNextUiTheme,
- isUiTheme,
- persistUiTheme,
- resolveInitialUiTheme,
- UI_THEME_STORAGE_KEY,
-} from "./theme";
-
-describe("UI theme preference", () => {
- test("defaults to dark when no stored preference exists", () => {
- const storage = new Map();
-
- expect(resolveInitialUiTheme(storage)).toBe("dark");
- });
-
- test("restores a valid stored preference", () => {
- const storage = new Map([[UI_THEME_STORAGE_KEY, "light"]]);
-
- expect(resolveInitialUiTheme(storage)).toBe("light");
- });
-
- test("ignores invalid stored preferences", () => {
- const storage = new Map([[UI_THEME_STORAGE_KEY, "solarized"]]);
-
- expect(resolveInitialUiTheme(storage)).toBe("dark");
- });
-
- test("falls back when stored preferences cannot be read", () => {
- const storage = {
- getItem() {
- throw new Error("storage blocked");
- },
- };
-
- expect(resolveInitialUiTheme(storage)).toBe("dark");
- });
-
- test("ignores persistence failures", () => {
- const storage = {
- setItem() {
- throw new Error("quota exceeded");
- },
- };
-
- expect(() => persistUiTheme("light", storage)).not.toThrow();
- });
-
- test("detects and toggles supported themes", () => {
- expect(isUiTheme("light")).toBe(true);
- expect(isUiTheme("dark")).toBe(true);
- expect(isUiTheme("contrast")).toBe(false);
- expect(getNextUiTheme("dark")).toBe("light");
- expect(getNextUiTheme("light")).toBe("dark");
- });
-});
diff --git a/packages/ui/src/theme.ts b/packages/ui/src/theme.ts
deleted file mode 100644
index 9f48df5..0000000
--- a/packages/ui/src/theme.ts
+++ /dev/null
@@ -1,68 +0,0 @@
-export const UI_THEME_STORAGE_KEY = "dashboard-ui-theme";
-
-export type UiTheme = "dark" | "light";
-
-type ReadableThemeStorage =
- | { getItem(key: string): string | null }
- | { get(key: string): string | undefined };
-
-type WritableThemeStorage =
- | { setItem(key: string, value: string): void }
- | { set(key: string, value: string): unknown };
-
-export function isUiTheme(value: unknown): value is UiTheme {
- return value === "dark" || value === "light";
-}
-
-export function getNextUiTheme(theme: UiTheme): UiTheme {
- return theme === "dark" ? "light" : "dark";
-}
-
-export function resolveInitialUiTheme(
- storage?: ReadableThemeStorage | null,
- fallback: UiTheme = "dark",
-): UiTheme {
- const stored = safeReadStoredTheme(storage);
-
- return isUiTheme(stored) ? stored : fallback;
-}
-
-export function persistUiTheme(
- theme: UiTheme,
- storage?: WritableThemeStorage | null,
-): void {
- if (!storage) return;
-
- try {
- if ("setItem" in storage) {
- storage.setItem(UI_THEME_STORAGE_KEY, theme);
- return;
- }
-
- storage.set(UI_THEME_STORAGE_KEY, theme);
- } catch {
- // Browser storage may be blocked or quota-constrained.
- }
-}
-
-function safeReadStoredTheme(
- storage?: ReadableThemeStorage | null,
-): string | undefined {
- try {
- return readStoredTheme(storage);
- } catch {
- return undefined;
- }
-}
-
-function readStoredTheme(
- storage?: ReadableThemeStorage | null,
-): string | undefined {
- if (!storage) return undefined;
-
- if ("getItem" in storage) {
- return storage.getItem(UI_THEME_STORAGE_KEY) ?? undefined;
- }
-
- return storage.get(UI_THEME_STORAGE_KEY);
-}
diff --git a/packages/ui/src/tokens.css b/packages/ui/src/tokens.css
deleted file mode 100644
index 26b20bd..0000000
--- a/packages/ui/src/tokens.css
+++ /dev/null
@@ -1,160 +0,0 @@
-:root,
-[data-ui-theme="dark"] {
- color-scheme: dark;
- --ui-color-background: #0b0f0d;
- --ui-color-backdrop-edge: #020403;
- --ui-color-canvas: #0b0f0d;
- --ui-color-surface: #111713;
- --ui-color-surface-raised: #151d18;
- --ui-color-surface-elevated: #19231d;
- --ui-color-surface-panel: rgba(15, 21, 17, 0.94);
- --ui-color-surface-module: rgba(18, 25, 21, 0.96);
- --ui-color-surface-footer: rgba(11, 15, 13, 0.98);
- --ui-color-surface-inset: #070b09;
- --ui-color-surface-icon: #101711;
- --ui-color-border-subtle: rgba(209, 222, 198, 0.13);
- --ui-color-border: rgba(211, 225, 199, 0.2);
- --ui-color-border-strong: rgba(219, 234, 207, 0.34);
- --ui-color-line: var(--ui-color-border);
- --ui-color-line-strong: var(--ui-color-border-strong);
- --ui-color-grid-line: rgba(222, 238, 211, 0.045);
- --ui-color-backdrop-vignette: rgba(0, 0, 0, 0.24);
- --ui-color-frame-spine: rgba(198, 230, 160, 0.1);
- --ui-color-frame-wash: rgba(10, 15, 12, 0.52);
- --ui-color-card-gradient-start: rgba(24, 33, 27, 0.94);
- --ui-color-card-gradient-end: rgba(10, 15, 12, 0.98);
- --ui-color-hover: rgba(190, 225, 146, 0.075);
- --ui-color-text-primary: #edf1e6;
- --ui-color-text-secondary: #b4bda9;
- --ui-color-text-muted: #7f8a7a;
- --ui-color-text: var(--ui-color-text-primary);
- --ui-color-muted: var(--ui-color-text-muted);
- --ui-color-dim: #4e594d;
- --ui-color-accent: #c7ef5f;
- --ui-color-accent-soft: rgba(199, 239, 95, 0.12);
- --ui-color-accent-contrast: #0a100b;
- --ui-color-success: #9fd85a;
- --ui-color-ok: var(--ui-color-success);
- --ui-color-warning: #d99a3a;
- --ui-color-critical: #f04463;
- --ui-color-danger: var(--ui-color-critical);
- --ui-color-info: #86a8bd;
- --ui-color-stale: #8a9a93;
- --ui-color-unavailable: #6f7a76;
- --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-border);
- --ui-border-strong: 1px solid var(--ui-color-border-strong);
- --ui-shadow-hard: 0 0 0 1px rgba(199, 239, 95, 0.1) inset;
- --ui-shadow-inset-panel:
- inset 0 1px 0 rgba(255, 255, 255, 0.04),
- inset 0 -1px 0 rgba(0, 0, 0, 0.32);
- --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);
-}
-
-[data-ui-theme="light"] {
- color-scheme: light;
- --ui-color-background: #eef2e7;
- --ui-color-backdrop-edge: #d7decf;
- --ui-color-canvas: #eef2e7;
- --ui-color-surface: #f9fbf3;
- --ui-color-surface-raised: #f1f5ea;
- --ui-color-surface-elevated: #ffffff;
- --ui-color-surface-panel: rgba(250, 252, 245, 0.94);
- --ui-color-surface-module: rgba(252, 254, 247, 0.96);
- --ui-color-surface-footer: rgba(239, 243, 233, 0.98);
- --ui-color-surface-inset: #e1e8d9;
- --ui-color-surface-icon: #edf3e6;
- --ui-color-border-subtle: rgba(32, 43, 29, 0.12);
- --ui-color-border: rgba(32, 43, 29, 0.22);
- --ui-color-border-strong: rgba(32, 43, 29, 0.38);
- --ui-color-line: var(--ui-color-border);
- --ui-color-line-strong: var(--ui-color-border-strong);
- --ui-color-grid-line: rgba(32, 43, 29, 0.055);
- --ui-color-backdrop-vignette: rgba(31, 44, 29, 0.08);
- --ui-color-frame-spine: rgba(32, 43, 29, 0.09);
- --ui-color-frame-wash: rgba(255, 255, 255, 0.54);
- --ui-color-card-gradient-start: rgba(255, 255, 255, 0.96);
- --ui-color-card-gradient-end: rgba(229, 235, 221, 0.92);
- --ui-color-hover: rgba(54, 81, 25, 0.065);
- --ui-color-text-primary: #11180f;
- --ui-color-text-secondary: #3f4b3b;
- --ui-color-text-muted: #667260;
- --ui-color-text: var(--ui-color-text-primary);
- --ui-color-muted: var(--ui-color-text-muted);
- --ui-color-dim: #7e8878;
- --ui-color-accent: #4f7200;
- --ui-color-accent-soft: rgba(79, 114, 0, 0.11);
- --ui-color-accent-contrast: #ffffff;
- --ui-color-success: #436f00;
- --ui-color-ok: var(--ui-color-success);
- --ui-color-warning: #915c00;
- --ui-color-critical: #b7173c;
- --ui-color-danger: var(--ui-color-critical);
- --ui-color-info: #476a7c;
- --ui-color-stale: #596b64;
- --ui-color-unavailable: #68726c;
- --ui-shadow-hard: 0 0 0 1px rgba(79, 114, 0, 0.14) inset;
- --ui-shadow-inset-panel:
- inset 0 1px 0 rgba(255, 255, 255, 0.72),
- inset 0 -1px 0 rgba(31, 44, 29, 0.08);
-}
-
-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;
-}
-
-@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/packages/ui/src/types.ts b/packages/ui/src/types.ts
deleted file mode 100644
index 792a24b..0000000
--- a/packages/ui/src/types.ts
+++ /dev/null
@@ -1,88 +0,0 @@
-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;
- layout?: "grid" | "list";
- 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/packages/ui/src/utils.ts b/packages/ui/src/utils.ts
deleted file mode 100644
index bd0c391..0000000
--- a/packages/ui/src/utils.ts
+++ /dev/null
@@ -1,6 +0,0 @@
-import { clsx, type ClassValue } from "clsx"
-import { twMerge } from "tailwind-merge"
-
-export function cn(...inputs: ClassValue[]) {
- return twMerge(clsx(inputs))
-}
diff --git a/packages/ui/tsconfig.build.json b/packages/ui/tsconfig.build.json
deleted file mode 100644
index bd56366..0000000
--- a/packages/ui/tsconfig.build.json
+++ /dev/null
@@ -1,19 +0,0 @@
-{
- "extends": "./tsconfig.json",
- "compilerOptions": {
- "declaration": true,
- "emitDeclarationOnly": false,
- "noEmit": false,
- "outDir": "dist",
- "rootDir": "src"
- },
- "include": ["src/**/*.ts", "src/**/*.tsx"],
- "exclude": [
- "dist",
- "node_modules",
- "storybook-static",
- "src/**/*.test.ts",
- "src/**/*.test.tsx",
- "src/stories/**"
- ]
-}
diff --git a/packages/ui/tsconfig.json b/packages/ui/tsconfig.json
deleted file mode 100644
index 2ad97bc..0000000
--- a/packages/ui/tsconfig.json
+++ /dev/null
@@ -1,8 +0,0 @@
-{
- "extends": "../../tsconfig.base.json",
- "compilerOptions": {
- "baseUrl": "."
- },
- "include": ["src/**/*.ts", "src/**/*.tsx", ".storybook/**/*.ts"],
- "exclude": ["dist", "node_modules", "storybook-static"]
-}
diff --git a/scripts/deploy-dimensionlab-website.sh b/scripts/deploy-dimensionlab-website.sh
new file mode 100755
index 0000000..4fac4cd
--- /dev/null
+++ b/scripts/deploy-dimensionlab-website.sh
@@ -0,0 +1,362 @@
+#!/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/turbo.json b/turbo.json
index f2d3137..e1b2e7c 100644
--- a/turbo.json
+++ b/turbo.json
@@ -9,6 +9,7 @@
"tasks": {
"build": {
"dependsOn": ["^build"],
+ "inputs": ["$TURBO_DEFAULT$", ".env*"],
"outputs": ["dist/**", "build/**"],
"env": ["NODE_ENV", "VITE_*"]
},
@@ -29,6 +30,7 @@
},
"build-storybook": {
"dependsOn": ["^build"],
+ "inputs": ["$TURBO_DEFAULT$", ".env*"],
"outputs": ["storybook-static/**"],
"env": ["NODE_ENV", "STORYBOOK_*", "VITE_*"]
},