diff --git a/README.md b/README.md
index 98dc8ec..572f9bc 100644
--- a/README.md
+++ b/README.md
@@ -16,7 +16,8 @@ validated dashboard model state inside the web app.
- `packages/dashboard-model`: reusable dashboard schema, validation, and
generic model fixtures shared by apps and tooling.
- `packages/ui`: reusable dashboard React components, design tokens,
- shadcn/radix primitives, generic fixtures, and Storybook.
+ 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.
## Development
diff --git a/packages/ui/src/components/Badge.tsx b/packages/ui/src/components/foundation/Badge.tsx
similarity index 84%
rename from packages/ui/src/components/Badge.tsx
rename to packages/ui/src/components/foundation/Badge.tsx
index a21ade8..5bdee48 100644
--- a/packages/ui/src/components/Badge.tsx
+++ b/packages/ui/src/components/foundation/Badge.tsx
@@ -1,4 +1,4 @@
-import type { UiSeverity } from "../types";
+import type { UiSeverity } from "../../types";
import { StatusBadge } from "./StatusBadge";
export interface BadgeProps {
diff --git a/packages/ui/src/components/Button.tsx b/packages/ui/src/components/foundation/Button.tsx
similarity index 100%
rename from packages/ui/src/components/Button.tsx
rename to packages/ui/src/components/foundation/Button.tsx
diff --git a/packages/ui/src/components/IconButton.tsx b/packages/ui/src/components/foundation/IconButton.tsx
similarity index 100%
rename from packages/ui/src/components/IconButton.tsx
rename to packages/ui/src/components/foundation/IconButton.tsx
diff --git a/packages/ui/src/components/IconGlyph.tsx b/packages/ui/src/components/foundation/IconGlyph.tsx
similarity index 100%
rename from packages/ui/src/components/IconGlyph.tsx
rename to packages/ui/src/components/foundation/IconGlyph.tsx
diff --git a/packages/ui/src/components/ProgressMeter.tsx b/packages/ui/src/components/foundation/ProgressMeter.tsx
similarity index 88%
rename from packages/ui/src/components/ProgressMeter.tsx
rename to packages/ui/src/components/foundation/ProgressMeter.tsx
index c7278bf..c3f3aad 100644
--- a/packages/ui/src/components/ProgressMeter.tsx
+++ b/packages/ui/src/components/foundation/ProgressMeter.tsx
@@ -1,6 +1,6 @@
import type { CSSProperties } from "react";
-import { clampPercent } from "../format";
-import type { UiSeverity } from "../types";
+import { clampPercent } from "../../format";
+import type { UiSeverity } from "../../types";
export interface ProgressMeterProps {
value?: number;
diff --git a/packages/ui/src/components/Separator.tsx b/packages/ui/src/components/foundation/Separator.tsx
similarity index 100%
rename from packages/ui/src/components/Separator.tsx
rename to packages/ui/src/components/foundation/Separator.tsx
diff --git a/packages/ui/src/components/StatusBadge.tsx b/packages/ui/src/components/foundation/StatusBadge.tsx
similarity index 85%
rename from packages/ui/src/components/StatusBadge.tsx
rename to packages/ui/src/components/foundation/StatusBadge.tsx
index fb63a04..d2b01b3 100644
--- a/packages/ui/src/components/StatusBadge.tsx
+++ b/packages/ui/src/components/foundation/StatusBadge.tsx
@@ -1,4 +1,4 @@
-import type { UiSeverity } from "../types";
+import type { UiSeverity } from "../../types";
export interface StatusBadgeProps {
label: string;
diff --git a/packages/ui/src/components/ThemeToggle.tsx b/packages/ui/src/components/foundation/ThemeToggle.tsx
similarity index 92%
rename from packages/ui/src/components/ThemeToggle.tsx
rename to packages/ui/src/components/foundation/ThemeToggle.tsx
index 9e883af..5d83031 100644
--- a/packages/ui/src/components/ThemeToggle.tsx
+++ b/packages/ui/src/components/foundation/ThemeToggle.tsx
@@ -1,5 +1,5 @@
-import type { UiTheme } from "../theme";
-import { getNextUiTheme } from "../theme";
+import type { UiTheme } from "../../theme";
+import { getNextUiTheme } from "../../theme";
import { IconGlyph } from "./IconGlyph";
export interface ThemeToggleProps {
diff --git a/packages/ui/src/components/CornerBracketFrame.tsx b/packages/ui/src/components/frames/CornerBracketFrame.tsx
similarity index 100%
rename from packages/ui/src/components/CornerBracketFrame.tsx
rename to packages/ui/src/components/frames/CornerBracketFrame.tsx
diff --git a/packages/ui/src/components/DashboardFrame.tsx b/packages/ui/src/components/frames/DashboardFrame.tsx
similarity index 88%
rename from packages/ui/src/components/DashboardFrame.tsx
rename to packages/ui/src/components/frames/DashboardFrame.tsx
index cdf312a..08029cf 100644
--- a/packages/ui/src/components/DashboardFrame.tsx
+++ b/packages/ui/src/components/frames/DashboardFrame.tsx
@@ -1,10 +1,10 @@
import { useId } from "react";
import type { ReactNode } from "react";
-import type { UiDashboardPreview } from "../types";
+import type { UiDashboardPreview } from "../../types";
import { ModuleCard } from "./ModuleCard";
-import { ServicePanel } from "./ServicePanel";
-import { StatusStrip } from "./StatusStrip";
-import { TelemetryGrid } from "./TelemetryGrid";
+import { ServicePanel } from "../operations/ServicePanel";
+import { StatusStrip } from "../operations/StatusStrip";
+import { TelemetryGrid } from "../telemetry/TelemetryGrid";
export interface DashboardFrameProps {
actions?: ReactNode;
diff --git a/packages/ui/src/components/DashboardHeader.tsx b/packages/ui/src/components/frames/DashboardHeader.tsx
similarity index 92%
rename from packages/ui/src/components/DashboardHeader.tsx
rename to packages/ui/src/components/frames/DashboardHeader.tsx
index 68b4e11..3f418fa 100644
--- a/packages/ui/src/components/DashboardHeader.tsx
+++ b/packages/ui/src/components/frames/DashboardHeader.tsx
@@ -1,5 +1,5 @@
import { useId } from "react";
-import type { UiModuleBlock } from "../types";
+import type { UiModuleBlock } from "../../types";
import { ModuleCard } from "./ModuleCard";
export interface DashboardHeaderProps {
diff --git a/packages/ui/src/components/DiagonalStripeField.tsx b/packages/ui/src/components/frames/DiagonalStripeField.tsx
similarity index 100%
rename from packages/ui/src/components/DiagonalStripeField.tsx
rename to packages/ui/src/components/frames/DiagonalStripeField.tsx
diff --git a/packages/ui/src/components/FooterCell.tsx b/packages/ui/src/components/frames/FooterCell.tsx
similarity index 94%
rename from packages/ui/src/components/FooterCell.tsx
rename to packages/ui/src/components/frames/FooterCell.tsx
index 760e853..8b9a86d 100644
--- a/packages/ui/src/components/FooterCell.tsx
+++ b/packages/ui/src/components/frames/FooterCell.tsx
@@ -1,4 +1,4 @@
-import type { UiStatusItem } from "../types";
+import type { UiStatusItem } from "../../types";
export interface FooterCellProps {
item: UiStatusItem;
diff --git a/packages/ui/src/components/FooterStatusCell.tsx b/packages/ui/src/components/frames/FooterStatusCell.tsx
similarity index 75%
rename from packages/ui/src/components/FooterStatusCell.tsx
rename to packages/ui/src/components/frames/FooterStatusCell.tsx
index dcfb8b4..b5b339e 100644
--- a/packages/ui/src/components/FooterStatusCell.tsx
+++ b/packages/ui/src/components/frames/FooterStatusCell.tsx
@@ -1,4 +1,4 @@
-import type { UiStatusItem } from "../types";
+import type { UiStatusItem } from "../../types";
import { FooterCell } from "./FooterCell";
export function FooterStatusCell({ item }: { item: UiStatusItem }) {
diff --git a/packages/ui/src/components/GridFrame.tsx b/packages/ui/src/components/frames/GridFrame.tsx
similarity index 100%
rename from packages/ui/src/components/GridFrame.tsx
rename to packages/ui/src/components/frames/GridFrame.tsx
diff --git a/packages/ui/src/components/ModuleCard.tsx b/packages/ui/src/components/frames/ModuleCard.tsx
similarity index 89%
rename from packages/ui/src/components/ModuleCard.tsx
rename to packages/ui/src/components/frames/ModuleCard.tsx
index d05bee0..9b96df9 100644
--- a/packages/ui/src/components/ModuleCard.tsx
+++ b/packages/ui/src/components/frames/ModuleCard.tsx
@@ -1,6 +1,6 @@
import { useId } from "react";
-import type { UiModuleBlock } from "../types";
-import { IconGlyph } from "./IconGlyph";
+import type { UiModuleBlock } from "../../types";
+import { IconGlyph } from "../foundation/IconGlyph";
export interface ModuleCardProps {
module: UiModuleBlock;
diff --git a/packages/ui/src/components/Panel.tsx b/packages/ui/src/components/frames/Panel.tsx
similarity index 100%
rename from packages/ui/src/components/Panel.tsx
rename to packages/ui/src/components/frames/Panel.tsx
diff --git a/packages/ui/src/components/ScanlineField.tsx b/packages/ui/src/components/frames/ScanlineField.tsx
similarity index 100%
rename from packages/ui/src/components/ScanlineField.tsx
rename to packages/ui/src/components/frames/ScanlineField.tsx
diff --git a/packages/ui/src/components/ServiceGroupPanel.tsx b/packages/ui/src/components/operations/ServiceGroupPanel.tsx
similarity index 76%
rename from packages/ui/src/components/ServiceGroupPanel.tsx
rename to packages/ui/src/components/operations/ServiceGroupPanel.tsx
index 4252cc9..9b53340 100644
--- a/packages/ui/src/components/ServiceGroupPanel.tsx
+++ b/packages/ui/src/components/operations/ServiceGroupPanel.tsx
@@ -1,4 +1,4 @@
-import type { UiServiceGroup } from "../types";
+import type { UiServiceGroup } from "../../types";
import { ServicePanel } from "./ServicePanel";
export function ServiceGroupPanel({ group }: { group: UiServiceGroup }) {
diff --git a/packages/ui/src/components/ServicePanel.tsx b/packages/ui/src/components/operations/ServicePanel.tsx
similarity index 87%
rename from packages/ui/src/components/ServicePanel.tsx
rename to packages/ui/src/components/operations/ServicePanel.tsx
index 12a2e74..c8b85af 100644
--- a/packages/ui/src/components/ServicePanel.tsx
+++ b/packages/ui/src/components/operations/ServicePanel.tsx
@@ -1,5 +1,5 @@
-import type { UiServiceGroup } from "../types";
-import { Panel } from "./Panel";
+import type { UiServiceGroup } from "../../types";
+import { Panel } from "../frames/Panel";
import { ServiceRow } from "./ServiceRow";
import { StatusStrip } from "./StatusStrip";
diff --git a/packages/ui/src/components/ServiceRow.tsx b/packages/ui/src/components/operations/ServiceRow.tsx
similarity index 89%
rename from packages/ui/src/components/ServiceRow.tsx
rename to packages/ui/src/components/operations/ServiceRow.tsx
index cf065f4..e6e5aa9 100644
--- a/packages/ui/src/components/ServiceRow.tsx
+++ b/packages/ui/src/components/operations/ServiceRow.tsx
@@ -1,6 +1,6 @@
-import type { UiServiceRow } from "../types";
-import { IconGlyph } from "./IconGlyph";
-import { StatusBadge } from "./StatusBadge";
+import type { UiServiceRow } from "../../types";
+import { IconGlyph } from "../foundation/IconGlyph";
+import { StatusBadge } from "../foundation/StatusBadge";
export interface ServiceRowProps {
service: UiServiceRow;
diff --git a/packages/ui/src/components/StatusStrip.tsx b/packages/ui/src/components/operations/StatusStrip.tsx
similarity index 82%
rename from packages/ui/src/components/StatusStrip.tsx
rename to packages/ui/src/components/operations/StatusStrip.tsx
index 66c6451..3f2bca2 100644
--- a/packages/ui/src/components/StatusStrip.tsx
+++ b/packages/ui/src/components/operations/StatusStrip.tsx
@@ -1,5 +1,5 @@
-import type { UiStatusItem } from "../types";
-import { FooterCell } from "./FooterCell";
+import type { UiStatusItem } from "../../types";
+import { FooterCell } from "../frames/FooterCell";
export interface StatusStripProps {
id?: string;
diff --git a/packages/ui/src/components/SystemState.tsx b/packages/ui/src/components/operations/SystemState.tsx
similarity index 82%
rename from packages/ui/src/components/SystemState.tsx
rename to packages/ui/src/components/operations/SystemState.tsx
index e817e2b..438fa52 100644
--- a/packages/ui/src/components/SystemState.tsx
+++ b/packages/ui/src/components/operations/SystemState.tsx
@@ -1,5 +1,5 @@
-import type { UiSeverity } from "../types";
-import { IconGlyph } from "./IconGlyph";
+import type { UiSeverity } from "../../types";
+import { IconGlyph } from "../foundation/IconGlyph";
export interface SystemStateProps {
title: string;
diff --git a/packages/ui/src/components/WeatherModule.tsx b/packages/ui/src/components/operations/WeatherModule.tsx
similarity index 53%
rename from packages/ui/src/components/WeatherModule.tsx
rename to packages/ui/src/components/operations/WeatherModule.tsx
index 0967cfb..c7e55db 100644
--- a/packages/ui/src/components/WeatherModule.tsx
+++ b/packages/ui/src/components/operations/WeatherModule.tsx
@@ -1,5 +1,5 @@
-import type { UiModuleBlock } from "../types";
-import { ModuleCard } from "./ModuleCard";
+import type { UiModuleBlock } from "../../types";
+import { ModuleCard } from "../frames/ModuleCard";
export function WeatherModule({ module }: { module: UiModuleBlock }) {
return ;
diff --git a/packages/ui/src/components/render.test.tsx b/packages/ui/src/components/render.test.tsx
index 1cf40fe..010f672 100644
--- a/packages/ui/src/components/render.test.tsx
+++ b/packages/ui/src/components/render.test.tsx
@@ -1,14 +1,16 @@
import { renderToString } from "react-dom/server";
import { describe, expect, test } from "vitest";
-import { Button } from "./Button";
-import { DashboardFrame } from "./DashboardFrame";
-import { FooterCell } from "./FooterCell";
-import { IconButton } from "./IconButton";
-import { ServiceRow } from "./ServiceRow";
-import { StatusStrip } from "./StatusStrip";
-import { TelemetryCard } from "./TelemetryCard";
-import { TelemetryGrid } from "./TelemetryGrid";
-import { ThemeToggle } from "./ThemeToggle";
+import {
+ Button,
+ DashboardFrame,
+ FooterCell,
+ IconButton,
+ ServiceRow,
+ StatusStrip,
+ TelemetryCard,
+ TelemetryGrid,
+ ThemeToggle,
+} from "../index";
import { dashboardPreviewFixtures } from "../fixtures";
describe("dashboard UI components", () => {
diff --git a/packages/ui/src/components/LineChart.tsx b/packages/ui/src/components/telemetry/LineChart.tsx
similarity index 98%
rename from packages/ui/src/components/LineChart.tsx
rename to packages/ui/src/components/telemetry/LineChart.tsx
index 386b515..0accdf7 100644
--- a/packages/ui/src/components/LineChart.tsx
+++ b/packages/ui/src/components/telemetry/LineChart.tsx
@@ -1,6 +1,6 @@
import "uplot/dist/uPlot.min.css";
import { useEffect, useRef } from "react";
-import type { UiSeverity } from "../types";
+import type { UiSeverity } from "../../types";
export interface LineChartProps {
values?: number[];
diff --git a/packages/ui/src/components/SignalTrace.tsx b/packages/ui/src/components/telemetry/SignalTrace.tsx
similarity index 100%
rename from packages/ui/src/components/SignalTrace.tsx
rename to packages/ui/src/components/telemetry/SignalTrace.tsx
diff --git a/packages/ui/src/components/Sparkline.tsx b/packages/ui/src/components/telemetry/Sparkline.tsx
similarity index 94%
rename from packages/ui/src/components/Sparkline.tsx
rename to packages/ui/src/components/telemetry/Sparkline.tsx
index a79f532..d03fa13 100644
--- a/packages/ui/src/components/Sparkline.tsx
+++ b/packages/ui/src/components/telemetry/Sparkline.tsx
@@ -1,4 +1,4 @@
-import type { UiSeverity } from "../types";
+import type { UiSeverity } from "../../types";
export interface SparklineProps {
values?: number[];
diff --git a/packages/ui/src/components/TelemetryCard.tsx b/packages/ui/src/components/telemetry/TelemetryCard.tsx
similarity index 91%
rename from packages/ui/src/components/TelemetryCard.tsx
rename to packages/ui/src/components/telemetry/TelemetryCard.tsx
index 97033c9..15481ee 100644
--- a/packages/ui/src/components/TelemetryCard.tsx
+++ b/packages/ui/src/components/telemetry/TelemetryCard.tsx
@@ -1,7 +1,7 @@
import type { CSSProperties } from "react";
-import { clampPercent, formatMetricValue } from "../format";
-import type { UiTelemetryCard } from "../types";
-import { IconGlyph } from "./IconGlyph";
+import { clampPercent, formatMetricValue } from "../../format";
+import type { UiTelemetryCard } from "../../types";
+import { IconGlyph } from "../foundation/IconGlyph";
import { LineChart } from "./LineChart";
export interface TelemetryCardProps {
diff --git a/packages/ui/src/components/TelemetryGrid.tsx b/packages/ui/src/components/telemetry/TelemetryGrid.tsx
similarity index 94%
rename from packages/ui/src/components/TelemetryGrid.tsx
rename to packages/ui/src/components/telemetry/TelemetryGrid.tsx
index 8afb9a2..9f4d497 100644
--- a/packages/ui/src/components/TelemetryGrid.tsx
+++ b/packages/ui/src/components/telemetry/TelemetryGrid.tsx
@@ -1,5 +1,5 @@
import { useId } from "react";
-import type { UiTelemetryCard } from "../types";
+import type { UiTelemetryCard } from "../../types";
import { TelemetryCard } from "./TelemetryCard";
export interface TelemetryGridProps {
diff --git a/packages/ui/src/components/TelemetryStrip.tsx b/packages/ui/src/components/telemetry/TelemetryStrip.tsx
similarity index 76%
rename from packages/ui/src/components/TelemetryStrip.tsx
rename to packages/ui/src/components/telemetry/TelemetryStrip.tsx
index 765c1e2..35073bb 100644
--- a/packages/ui/src/components/TelemetryStrip.tsx
+++ b/packages/ui/src/components/telemetry/TelemetryStrip.tsx
@@ -1,4 +1,4 @@
-import type { UiTelemetryCard } from "../types";
+import type { UiTelemetryCard } from "../../types";
import { TelemetryGrid } from "./TelemetryGrid";
export function TelemetryStrip({ cards }: { cards: UiTelemetryCard[] }) {
diff --git a/packages/ui/src/content-boundary.test.ts b/packages/ui/src/content-boundary.test.ts
index 80b85ce..f7188a1 100644
--- a/packages/ui/src/content-boundary.test.ts
+++ b/packages/ui/src/content-boundary.test.ts
@@ -26,15 +26,28 @@ const forbiddenTerms = [
describe("UI package content boundary", () => {
test("contains the reusable dashboard component inventory", () => {
expect(existsSync(join(uiSourceRoot, "index.ts"))).toBe(true);
- expect(existsSync(join(uiSourceRoot, "components/DashboardFrame.tsx"))).toBe(
- true,
- );
- expect(existsSync(join(uiSourceRoot, "components/ThemeToggle.tsx"))).toBe(
- true,
- );
+ expect(
+ existsSync(join(uiSourceRoot, "components/frames/DashboardFrame.tsx")),
+ ).toBe(true);
+ expect(
+ existsSync(join(uiSourceRoot, "components/foundation/ThemeToggle.tsx")),
+ ).toBe(true);
expect(existsSync(join(uiSourceRoot, "styles.css"))).toBe(true);
});
+ test("keeps UI components grouped by domain instead of a flat bucket", () => {
+ const domainFolders = ["foundation", "frames", "operations", "telemetry"];
+
+ for (const folder of domainFolders) {
+ expect(existsSync(join(uiSourceRoot, "components", folder))).toBe(true);
+ }
+
+ const flatComponentFiles = readdirSync(join(uiSourceRoot, "components"))
+ .filter((entry) => entry.endsWith(".tsx") && !entry.endsWith(".test.tsx"))
+ .sort();
+ expect(flatComponentFiles).toEqual([]);
+ });
+
test("keeps environment-specific content out of reusable UI source", () => {
const source = readUiSource(uiSourceRoot).toLowerCase();
diff --git a/packages/ui/src/index.ts b/packages/ui/src/index.ts
index a9a83df..bc506c0 100644
--- a/packages/ui/src/index.ts
+++ b/packages/ui/src/index.ts
@@ -1,33 +1,33 @@
-export { Badge } from "./components/Badge";
-export { Button } from "./components/Button";
-export { CornerBracketFrame } from "./components/CornerBracketFrame";
-export { DashboardHeader } from "./components/DashboardHeader";
-export { DashboardFrame } from "./components/DashboardFrame";
-export { DiagonalStripeField } from "./components/DiagonalStripeField";
-export { FooterCell } from "./components/FooterCell";
-export { FooterStatusCell } from "./components/FooterStatusCell";
-export { GridFrame } from "./components/GridFrame";
-export { IconGlyph } from "./components/IconGlyph";
-export { IconButton } from "./components/IconButton";
-export { LineChart } from "./components/LineChart";
-export { ModuleCard } from "./components/ModuleCard";
-export { Panel } from "./components/Panel";
-export { ProgressMeter } from "./components/ProgressMeter";
-export { Separator } from "./components/Separator";
-export { ServiceGroupPanel } from "./components/ServiceGroupPanel";
-export { ServicePanel } from "./components/ServicePanel";
-export { ServiceRow } from "./components/ServiceRow";
-export { ScanlineField } from "./components/ScanlineField";
-export { SignalTrace } from "./components/SignalTrace";
-export { Sparkline } from "./components/Sparkline";
-export { StatusBadge } from "./components/StatusBadge";
-export { StatusStrip } from "./components/StatusStrip";
-export { SystemState } from "./components/SystemState";
-export { TelemetryCard } from "./components/TelemetryCard";
-export { TelemetryGrid } from "./components/TelemetryGrid";
-export { TelemetryStrip } from "./components/TelemetryStrip";
-export { ThemeToggle } from "./components/ThemeToggle";
-export { WeatherModule } from "./components/WeatherModule";
+export { Badge } from "./components/foundation/Badge";
+export { Button } from "./components/foundation/Button";
+export { IconGlyph } from "./components/foundation/IconGlyph";
+export { IconButton } from "./components/foundation/IconButton";
+export { ProgressMeter } from "./components/foundation/ProgressMeter";
+export { Separator } from "./components/foundation/Separator";
+export { StatusBadge } from "./components/foundation/StatusBadge";
+export { ThemeToggle } from "./components/foundation/ThemeToggle";
+export { CornerBracketFrame } from "./components/frames/CornerBracketFrame";
+export { DashboardHeader } from "./components/frames/DashboardHeader";
+export { DashboardFrame } from "./components/frames/DashboardFrame";
+export { DiagonalStripeField } from "./components/frames/DiagonalStripeField";
+export { FooterCell } from "./components/frames/FooterCell";
+export { FooterStatusCell } from "./components/frames/FooterStatusCell";
+export { GridFrame } from "./components/frames/GridFrame";
+export { ModuleCard } from "./components/frames/ModuleCard";
+export { Panel } from "./components/frames/Panel";
+export { ScanlineField } from "./components/frames/ScanlineField";
+export { ServiceGroupPanel } from "./components/operations/ServiceGroupPanel";
+export { ServicePanel } from "./components/operations/ServicePanel";
+export { ServiceRow } from "./components/operations/ServiceRow";
+export { StatusStrip } from "./components/operations/StatusStrip";
+export { SystemState } from "./components/operations/SystemState";
+export { WeatherModule } from "./components/operations/WeatherModule";
+export { LineChart } from "./components/telemetry/LineChart";
+export { SignalTrace } from "./components/telemetry/SignalTrace";
+export { Sparkline } from "./components/telemetry/Sparkline";
+export { TelemetryCard } from "./components/telemetry/TelemetryCard";
+export { TelemetryGrid } from "./components/telemetry/TelemetryGrid";
+export { TelemetryStrip } from "./components/telemetry/TelemetryStrip";
export { dashboardPreviewFixtures } from "./fixtures";
export {
getNextUiTheme,
diff --git a/packages/ui/src/stories/Badge.stories.tsx b/packages/ui/src/stories/Badge.stories.tsx
index d33394d..26e3ea8 100644
--- a/packages/ui/src/stories/Badge.stories.tsx
+++ b/packages/ui/src/stories/Badge.stories.tsx
@@ -1,5 +1,5 @@
import type { Meta, StoryObj } from "@storybook/react-vite";
-import { Badge } from "../components/Badge";
+import { Badge } from "../index";
const meta = {
title: "UI/Badge",
diff --git a/packages/ui/src/stories/Button.stories.tsx b/packages/ui/src/stories/Button.stories.tsx
index c207233..084caa8 100644
--- a/packages/ui/src/stories/Button.stories.tsx
+++ b/packages/ui/src/stories/Button.stories.tsx
@@ -1,5 +1,5 @@
import type { Meta, StoryObj } from "@storybook/react-vite";
-import { Button } from "../components/Button";
+import { Button } from "../index";
const meta = {
title: "UI/Button",
diff --git a/packages/ui/src/stories/CornerBracketFrame.stories.tsx b/packages/ui/src/stories/CornerBracketFrame.stories.tsx
index dd7efbf..d5c0110 100644
--- a/packages/ui/src/stories/CornerBracketFrame.stories.tsx
+++ b/packages/ui/src/stories/CornerBracketFrame.stories.tsx
@@ -1,5 +1,5 @@
import type { Meta, StoryObj } from "@storybook/react-vite";
-import { CornerBracketFrame } from "../components/CornerBracketFrame";
+import { CornerBracketFrame } from "../index";
const meta = {
title: "UI/CornerBracketFrame",
diff --git a/packages/ui/src/stories/DashboardFrame.stories.tsx b/packages/ui/src/stories/DashboardFrame.stories.tsx
index e1963af..894219e 100644
--- a/packages/ui/src/stories/DashboardFrame.stories.tsx
+++ b/packages/ui/src/stories/DashboardFrame.stories.tsx
@@ -1,5 +1,5 @@
import type { Meta, StoryObj } from "@storybook/react-vite";
-import { DashboardFrame } from "../components/DashboardFrame";
+import { DashboardFrame } from "../index";
import { fullCompositionDashboard, secondaryDashboard } from "./story-data";
const meta = {
diff --git a/packages/ui/src/stories/DashboardHeader.stories.tsx b/packages/ui/src/stories/DashboardHeader.stories.tsx
index 8263dd1..c337158 100644
--- a/packages/ui/src/stories/DashboardHeader.stories.tsx
+++ b/packages/ui/src/stories/DashboardHeader.stories.tsx
@@ -1,5 +1,5 @@
import type { Meta, StoryObj } from "@storybook/react-vite";
-import { DashboardHeader } from "../components/DashboardHeader";
+import { DashboardHeader } from "../index";
import { moduleBlocks } from "./story-data";
const meta = {
diff --git a/packages/ui/src/stories/DashboardOnePager.stories.tsx b/packages/ui/src/stories/DashboardOnePager.stories.tsx
index c44364b..3bd800f 100644
--- a/packages/ui/src/stories/DashboardOnePager.stories.tsx
+++ b/packages/ui/src/stories/DashboardOnePager.stories.tsx
@@ -1,5 +1,5 @@
import type { Meta, StoryObj } from "@storybook/react-vite";
-import { DashboardFrame } from "../components/DashboardFrame";
+import { DashboardFrame } from "../index";
import { fullCompositionDashboard } from "./story-data";
const meta = {
diff --git a/packages/ui/src/stories/DiagonalStripeField.stories.tsx b/packages/ui/src/stories/DiagonalStripeField.stories.tsx
index 4ca01f2..1f094ee 100644
--- a/packages/ui/src/stories/DiagonalStripeField.stories.tsx
+++ b/packages/ui/src/stories/DiagonalStripeField.stories.tsx
@@ -1,5 +1,5 @@
import type { Meta, StoryObj } from "@storybook/react-vite";
-import { DiagonalStripeField } from "../components/DiagonalStripeField";
+import { DiagonalStripeField } from "../index";
const meta = {
title: "UI/DiagonalStripeField",
diff --git a/packages/ui/src/stories/FooterCell.stories.tsx b/packages/ui/src/stories/FooterCell.stories.tsx
index 3ba26c7..7aa06d8 100644
--- a/packages/ui/src/stories/FooterCell.stories.tsx
+++ b/packages/ui/src/stories/FooterCell.stories.tsx
@@ -1,5 +1,5 @@
import type { Meta, StoryObj } from "@storybook/react-vite";
-import { FooterCell } from "../components/FooterCell";
+import { FooterCell } from "../index";
import { statusItems } from "./story-data";
const meta = {
diff --git a/packages/ui/src/stories/FooterStatusCell.stories.tsx b/packages/ui/src/stories/FooterStatusCell.stories.tsx
index 8097ba3..bd985a1 100644
--- a/packages/ui/src/stories/FooterStatusCell.stories.tsx
+++ b/packages/ui/src/stories/FooterStatusCell.stories.tsx
@@ -1,5 +1,5 @@
import type { Meta, StoryObj } from "@storybook/react-vite";
-import { FooterStatusCell } from "../components/FooterStatusCell";
+import { FooterStatusCell } from "../index";
import { statusItems } from "./story-data";
const meta = {
diff --git a/packages/ui/src/stories/GridFrame.stories.tsx b/packages/ui/src/stories/GridFrame.stories.tsx
index a43ad22..191bf6c 100644
--- a/packages/ui/src/stories/GridFrame.stories.tsx
+++ b/packages/ui/src/stories/GridFrame.stories.tsx
@@ -1,6 +1,5 @@
import type { Meta, StoryObj } from "@storybook/react-vite";
-import { GridFrame } from "../components/GridFrame";
-import { ModuleCard } from "../components/ModuleCard";
+import { GridFrame, ModuleCard } from "../index";
import { moduleBlocks } from "./story-data";
const meta = {
diff --git a/packages/ui/src/stories/IconButton.stories.tsx b/packages/ui/src/stories/IconButton.stories.tsx
index 35b58b9..612bf2a 100644
--- a/packages/ui/src/stories/IconButton.stories.tsx
+++ b/packages/ui/src/stories/IconButton.stories.tsx
@@ -1,5 +1,5 @@
import type { Meta, StoryObj } from "@storybook/react-vite";
-import { IconButton } from "../components/IconButton";
+import { IconButton } from "../index";
const meta = {
title: "UI/IconButton",
diff --git a/packages/ui/src/stories/IconGlyph.stories.tsx b/packages/ui/src/stories/IconGlyph.stories.tsx
index 3f1715a..d832951 100644
--- a/packages/ui/src/stories/IconGlyph.stories.tsx
+++ b/packages/ui/src/stories/IconGlyph.stories.tsx
@@ -1,5 +1,5 @@
import type { Meta, StoryObj } from "@storybook/react-vite";
-import { IconGlyph } from "../components/IconGlyph";
+import { IconGlyph } from "../index";
const meta = {
title: "UI/IconGlyph",
diff --git a/packages/ui/src/stories/LineChart.stories.tsx b/packages/ui/src/stories/LineChart.stories.tsx
index bbf8690..ccbd502 100644
--- a/packages/ui/src/stories/LineChart.stories.tsx
+++ b/packages/ui/src/stories/LineChart.stories.tsx
@@ -1,5 +1,5 @@
import type { Meta, StoryObj } from "@storybook/react-vite";
-import { LineChart } from "../components/LineChart";
+import { LineChart } from "../index";
const meta = {
title: "UI/LineChart",
diff --git a/packages/ui/src/stories/ModuleCard.stories.tsx b/packages/ui/src/stories/ModuleCard.stories.tsx
index 3366a55..ea0305b 100644
--- a/packages/ui/src/stories/ModuleCard.stories.tsx
+++ b/packages/ui/src/stories/ModuleCard.stories.tsx
@@ -1,5 +1,5 @@
import type { Meta, StoryObj } from "@storybook/react-vite";
-import { ModuleCard } from "../components/ModuleCard";
+import { ModuleCard } from "../index";
import { moduleBlocks } from "./story-data";
const meta = {
diff --git a/packages/ui/src/stories/Panel.stories.tsx b/packages/ui/src/stories/Panel.stories.tsx
index 730c2d9..91eb61e 100644
--- a/packages/ui/src/stories/Panel.stories.tsx
+++ b/packages/ui/src/stories/Panel.stories.tsx
@@ -1,6 +1,5 @@
import type { Meta, StoryObj } from "@storybook/react-vite";
-import { Panel } from "../components/Panel";
-import { ServiceRow } from "../components/ServiceRow";
+import { Panel, ServiceRow } from "../index";
import { serviceRows } from "./story-data";
const meta = {
diff --git a/packages/ui/src/stories/ProgressMeter.stories.tsx b/packages/ui/src/stories/ProgressMeter.stories.tsx
index d3bfdac..eec35c2 100644
--- a/packages/ui/src/stories/ProgressMeter.stories.tsx
+++ b/packages/ui/src/stories/ProgressMeter.stories.tsx
@@ -1,5 +1,5 @@
import type { Meta, StoryObj } from "@storybook/react-vite";
-import { ProgressMeter } from "../components/ProgressMeter";
+import { ProgressMeter } from "../index";
const meta = {
title: "UI/ProgressMeter",
diff --git a/packages/ui/src/stories/ScanlineField.stories.tsx b/packages/ui/src/stories/ScanlineField.stories.tsx
index 89e554e..fbe53f4 100644
--- a/packages/ui/src/stories/ScanlineField.stories.tsx
+++ b/packages/ui/src/stories/ScanlineField.stories.tsx
@@ -1,5 +1,5 @@
import type { Meta, StoryObj } from "@storybook/react-vite";
-import { ScanlineField } from "../components/ScanlineField";
+import { ScanlineField } from "../index";
const meta = {
title: "UI/ScanlineField",
diff --git a/packages/ui/src/stories/Separator.stories.tsx b/packages/ui/src/stories/Separator.stories.tsx
index e261f52..986ed23 100644
--- a/packages/ui/src/stories/Separator.stories.tsx
+++ b/packages/ui/src/stories/Separator.stories.tsx
@@ -1,5 +1,5 @@
import type { Meta, StoryObj } from "@storybook/react-vite";
-import { Separator } from "../components/Separator";
+import { Separator } from "../index";
const meta = {
title: "UI/Separator",
diff --git a/packages/ui/src/stories/ServiceGroupPanel.stories.tsx b/packages/ui/src/stories/ServiceGroupPanel.stories.tsx
index 7183771..f5ebdce 100644
--- a/packages/ui/src/stories/ServiceGroupPanel.stories.tsx
+++ b/packages/ui/src/stories/ServiceGroupPanel.stories.tsx
@@ -1,5 +1,5 @@
import type { Meta, StoryObj } from "@storybook/react-vite";
-import { ServiceGroupPanel } from "../components/ServiceGroupPanel";
+import { ServiceGroupPanel } from "../index";
import { serviceGroups } from "./story-data";
const meta = {
diff --git a/packages/ui/src/stories/ServicePanel.stories.tsx b/packages/ui/src/stories/ServicePanel.stories.tsx
index 750c428..d29b7ad 100644
--- a/packages/ui/src/stories/ServicePanel.stories.tsx
+++ b/packages/ui/src/stories/ServicePanel.stories.tsx
@@ -1,5 +1,5 @@
import type { Meta, StoryObj } from "@storybook/react-vite";
-import { ServicePanel } from "../components/ServicePanel";
+import { ServicePanel } from "../index";
import { serviceGroups } from "./story-data";
const meta = {
diff --git a/packages/ui/src/stories/ServiceRow.stories.tsx b/packages/ui/src/stories/ServiceRow.stories.tsx
index 9513d2c..56ba076 100644
--- a/packages/ui/src/stories/ServiceRow.stories.tsx
+++ b/packages/ui/src/stories/ServiceRow.stories.tsx
@@ -1,5 +1,5 @@
import type { Meta, StoryObj } from "@storybook/react-vite";
-import { ServiceRow } from "../components/ServiceRow";
+import { ServiceRow } from "../index";
import { serviceRows } from "./story-data";
const meta = {
diff --git a/packages/ui/src/stories/SignalTrace.stories.tsx b/packages/ui/src/stories/SignalTrace.stories.tsx
index ae8785b..4089df8 100644
--- a/packages/ui/src/stories/SignalTrace.stories.tsx
+++ b/packages/ui/src/stories/SignalTrace.stories.tsx
@@ -1,5 +1,5 @@
import type { Meta, StoryObj } from "@storybook/react-vite";
-import { SignalTrace } from "../components/SignalTrace";
+import { SignalTrace } from "../index";
const meta = {
title: "UI/SignalTrace",
diff --git a/packages/ui/src/stories/Sparkline.stories.tsx b/packages/ui/src/stories/Sparkline.stories.tsx
index efecfbd..af3e6e5 100644
--- a/packages/ui/src/stories/Sparkline.stories.tsx
+++ b/packages/ui/src/stories/Sparkline.stories.tsx
@@ -1,5 +1,5 @@
import type { Meta, StoryObj } from "@storybook/react-vite";
-import { Sparkline } from "../components/Sparkline";
+import { Sparkline } from "../index";
const meta = {
title: "UI/Sparkline",
diff --git a/packages/ui/src/stories/StatusBadge.stories.tsx b/packages/ui/src/stories/StatusBadge.stories.tsx
index 4513a08..92a444d 100644
--- a/packages/ui/src/stories/StatusBadge.stories.tsx
+++ b/packages/ui/src/stories/StatusBadge.stories.tsx
@@ -1,5 +1,5 @@
import type { Meta, StoryObj } from "@storybook/react-vite";
-import { StatusBadge } from "../components/StatusBadge";
+import { StatusBadge } from "../index";
const meta = {
title: "UI/StatusBadge",
diff --git a/packages/ui/src/stories/StatusStrip.stories.tsx b/packages/ui/src/stories/StatusStrip.stories.tsx
index 7b808fa..2fc236d 100644
--- a/packages/ui/src/stories/StatusStrip.stories.tsx
+++ b/packages/ui/src/stories/StatusStrip.stories.tsx
@@ -1,5 +1,5 @@
import type { Meta, StoryObj } from "@storybook/react-vite";
-import { StatusStrip } from "../components/StatusStrip";
+import { StatusStrip } from "../index";
import { mixedStatusStrip } from "./story-data";
const meta = {
diff --git a/packages/ui/src/stories/SystemState.stories.tsx b/packages/ui/src/stories/SystemState.stories.tsx
index 585540e..9b3f6e5 100644
--- a/packages/ui/src/stories/SystemState.stories.tsx
+++ b/packages/ui/src/stories/SystemState.stories.tsx
@@ -1,5 +1,5 @@
import type { Meta, StoryObj } from "@storybook/react-vite";
-import { SystemState } from "../components/SystemState";
+import { SystemState } from "../index";
const meta = {
title: "UI/SystemState",
diff --git a/packages/ui/src/stories/TelemetryCard.stories.tsx b/packages/ui/src/stories/TelemetryCard.stories.tsx
index dbdf301..3ac0e1e 100644
--- a/packages/ui/src/stories/TelemetryCard.stories.tsx
+++ b/packages/ui/src/stories/TelemetryCard.stories.tsx
@@ -1,5 +1,5 @@
import type { Meta, StoryObj } from "@storybook/react-vite";
-import { TelemetryCard } from "../components/TelemetryCard";
+import { TelemetryCard } from "../index";
import { telemetryCards } from "./story-data";
const meta = {
diff --git a/packages/ui/src/stories/TelemetryGrid.stories.tsx b/packages/ui/src/stories/TelemetryGrid.stories.tsx
index 6b0166e..34493c5 100644
--- a/packages/ui/src/stories/TelemetryGrid.stories.tsx
+++ b/packages/ui/src/stories/TelemetryGrid.stories.tsx
@@ -1,5 +1,5 @@
import type { Meta, StoryObj } from "@storybook/react-vite";
-import { TelemetryGrid } from "../components/TelemetryGrid";
+import { TelemetryGrid } from "../index";
import { eightTelemetryCards, sixteenTelemetryCards } from "./story-data";
const meta = {
diff --git a/packages/ui/src/stories/TelemetryStrip.stories.tsx b/packages/ui/src/stories/TelemetryStrip.stories.tsx
index 76fc983..f7c9d13 100644
--- a/packages/ui/src/stories/TelemetryStrip.stories.tsx
+++ b/packages/ui/src/stories/TelemetryStrip.stories.tsx
@@ -1,5 +1,5 @@
import type { Meta, StoryObj } from "@storybook/react-vite";
-import { TelemetryStrip } from "../components/TelemetryStrip";
+import { TelemetryStrip } from "../index";
import { eightTelemetryCards } from "./story-data";
const meta = {
diff --git a/packages/ui/src/stories/ThemeToggle.stories.tsx b/packages/ui/src/stories/ThemeToggle.stories.tsx
index 33e494a..f2edeea 100644
--- a/packages/ui/src/stories/ThemeToggle.stories.tsx
+++ b/packages/ui/src/stories/ThemeToggle.stories.tsx
@@ -1,5 +1,5 @@
import type { Meta, StoryObj } from "@storybook/react-vite";
-import { ThemeToggle } from "../components/ThemeToggle";
+import { ThemeToggle } from "../index";
const meta = {
title: "UI/ThemeToggle",
diff --git a/packages/ui/src/stories/WeatherModule.stories.tsx b/packages/ui/src/stories/WeatherModule.stories.tsx
index 6429de2..a376448 100644
--- a/packages/ui/src/stories/WeatherModule.stories.tsx
+++ b/packages/ui/src/stories/WeatherModule.stories.tsx
@@ -1,5 +1,5 @@
import type { Meta, StoryObj } from "@storybook/react-vite";
-import { WeatherModule } from "../components/WeatherModule";
+import { WeatherModule } from "../index";
import { moduleBlocks } from "./story-data";
const meta = {
diff --git a/packages/ui/src/storybook.test.ts b/packages/ui/src/storybook.test.ts
index 4ef14ba..49a9e83 100644
--- a/packages/ui/src/storybook.test.ts
+++ b/packages/ui/src/storybook.test.ts
@@ -9,6 +9,56 @@ const packageRoot = existsSync(join(root, "packages/ui/package.json"))
const componentsDir = join(packageRoot, "src/components");
const storiesDir = join(packageRoot, "src/stories");
+const componentDomains = {
+ foundation: [
+ "Badge",
+ "Button",
+ "IconButton",
+ "IconGlyph",
+ "ProgressMeter",
+ "Separator",
+ "StatusBadge",
+ "ThemeToggle",
+ ],
+ frames: [
+ "CornerBracketFrame",
+ "DashboardFrame",
+ "DashboardHeader",
+ "DiagonalStripeField",
+ "FooterCell",
+ "FooterStatusCell",
+ "GridFrame",
+ "ModuleCard",
+ "Panel",
+ "ScanlineField",
+ ],
+ operations: [
+ "ServiceGroupPanel",
+ "ServicePanel",
+ "ServiceRow",
+ "StatusStrip",
+ "SystemState",
+ "WeatherModule",
+ ],
+ telemetry: [
+ "LineChart",
+ "SignalTrace",
+ "Sparkline",
+ "TelemetryCard",
+ "TelemetryGrid",
+ "TelemetryStrip",
+ ],
+} as const;
+
+const componentFiles: ReadonlyMap = new Map(
+ Object.entries(componentDomains).flatMap(([domain, components]) =>
+ components.map((component) => [
+ component,
+ join(componentsDir, domain, `${component}.tsx`),
+ ]),
+ ),
+);
+
const requiredStoryFiles = [
"Badge.stories.tsx",
"Button.stories.tsx",
@@ -69,10 +119,7 @@ describe("Storybook inventory", () => {
test("loads dashboard component styles through the global app stylesheet", () => {
const packageStyles = readFileSync(join(packageRoot, "src/styles.css"), "utf8");
- const dashboardFrame = readFileSync(
- join(componentsDir, "DashboardFrame.tsx"),
- "utf8",
- );
+ const dashboardFrame = readFileSync(requiredComponentPath("DashboardFrame"), "utf8");
expect(packageStyles).toContain('./components/styles.css');
expect(dashboardFrame).not.toContain('./styles.css');
@@ -87,9 +134,9 @@ describe("Storybook inventory", () => {
});
test("keeps component and story files paired as the UI inventory changes", () => {
- const componentStoryFiles = readdirSync(componentsDir)
- .filter((filename) => filename.endsWith(".tsx") && !filename.endsWith(".test.tsx"))
- .map((filename) => filename.replace(".tsx", ".stories.tsx"));
+ const componentStoryFiles = [...componentFiles.keys()].map(
+ (component) => `${component}.stories.tsx`,
+ );
for (const filename of componentStoryFiles) {
expect(existsSync(join(storiesDir, filename)), `${filename} is missing`).toBe(true);
@@ -114,9 +161,7 @@ describe("Storybook inventory", () => {
"ScanlineField",
"SignalTrace",
]) {
- expect(existsSync(join(componentsDir, `${component}.tsx`)), `${component} is missing`).toBe(
- true,
- );
+ expect(existsSync(requiredComponentPath(component)), `${component} is missing`).toBe(true);
expect(
existsSync(join(storiesDir, `${component}.stories.tsx`)),
`${component}.stories.tsx is missing`,
@@ -131,7 +176,7 @@ describe("Storybook inventory", () => {
"ScanlineField",
"SignalTrace",
]) {
- const source = readFileSync(join(componentsDir, `${component}.tsx`), "utf8");
+ const source = readFileSync(requiredComponentPath(component), "utf8");
expect(source).toContain('aria-hidden="true"');
}
@@ -139,6 +184,7 @@ describe("Storybook inventory", () => {
test("does not add deferred form/navigation primitives", () => {
for (const component of ["Input", "ToggleGroup", "ScrollArea"]) {
+ expect(componentFiles.has(component)).toBe(false);
expect(existsSync(join(componentsDir, `${component}.tsx`))).toBe(false);
expect(existsSync(join(storiesDir, `${component}.stories.tsx`))).toBe(false);
}
@@ -153,3 +199,13 @@ describe("Storybook inventory", () => {
expect(legacyStories).toEqual([]);
});
});
+
+function requiredComponentPath(component: string): string {
+ const componentPath = componentFiles.get(component);
+
+ expect(componentPath, `${component} is missing from the UI component map`).toBeTypeOf(
+ "string",
+ );
+
+ return componentPath as string;
+}