diff --git a/README.md b/README.md index 572f9bc..98dc8ec 100644 --- a/README.md +++ b/README.md @@ -16,8 +16,7 @@ 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. Component source is - grouped under `foundation`, `frames`, `operations`, and `telemetry` domains. + shadcn/radix primitives, generic fixtures, and Storybook. - `docs/superpowers`: migration specs and execution plans used for this repo. ## Development diff --git a/packages/ui/src/components/foundation/Badge.tsx b/packages/ui/src/components/Badge.tsx similarity index 84% rename from packages/ui/src/components/foundation/Badge.tsx rename to packages/ui/src/components/Badge.tsx index 5bdee48..a21ade8 100644 --- a/packages/ui/src/components/foundation/Badge.tsx +++ b/packages/ui/src/components/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/foundation/Button.tsx b/packages/ui/src/components/Button.tsx similarity index 100% rename from packages/ui/src/components/foundation/Button.tsx rename to packages/ui/src/components/Button.tsx diff --git a/packages/ui/src/components/frames/CornerBracketFrame.tsx b/packages/ui/src/components/CornerBracketFrame.tsx similarity index 100% rename from packages/ui/src/components/frames/CornerBracketFrame.tsx rename to packages/ui/src/components/CornerBracketFrame.tsx diff --git a/packages/ui/src/components/frames/DashboardFrame.tsx b/packages/ui/src/components/DashboardFrame.tsx similarity index 88% rename from packages/ui/src/components/frames/DashboardFrame.tsx rename to packages/ui/src/components/DashboardFrame.tsx index 08029cf..cdf312a 100644 --- a/packages/ui/src/components/frames/DashboardFrame.tsx +++ b/packages/ui/src/components/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 "../operations/ServicePanel"; -import { StatusStrip } from "../operations/StatusStrip"; -import { TelemetryGrid } from "../telemetry/TelemetryGrid"; +import { ServicePanel } from "./ServicePanel"; +import { StatusStrip } from "./StatusStrip"; +import { TelemetryGrid } from "./TelemetryGrid"; export interface DashboardFrameProps { actions?: ReactNode; diff --git a/packages/ui/src/components/frames/DashboardHeader.tsx b/packages/ui/src/components/DashboardHeader.tsx similarity index 92% rename from packages/ui/src/components/frames/DashboardHeader.tsx rename to packages/ui/src/components/DashboardHeader.tsx index 3f418fa..68b4e11 100644 --- a/packages/ui/src/components/frames/DashboardHeader.tsx +++ b/packages/ui/src/components/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/frames/DiagonalStripeField.tsx b/packages/ui/src/components/DiagonalStripeField.tsx similarity index 100% rename from packages/ui/src/components/frames/DiagonalStripeField.tsx rename to packages/ui/src/components/DiagonalStripeField.tsx diff --git a/packages/ui/src/components/frames/FooterCell.tsx b/packages/ui/src/components/FooterCell.tsx similarity index 94% rename from packages/ui/src/components/frames/FooterCell.tsx rename to packages/ui/src/components/FooterCell.tsx index 8b9a86d..760e853 100644 --- a/packages/ui/src/components/frames/FooterCell.tsx +++ b/packages/ui/src/components/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/frames/FooterStatusCell.tsx b/packages/ui/src/components/FooterStatusCell.tsx similarity index 75% rename from packages/ui/src/components/frames/FooterStatusCell.tsx rename to packages/ui/src/components/FooterStatusCell.tsx index b5b339e..dcfb8b4 100644 --- a/packages/ui/src/components/frames/FooterStatusCell.tsx +++ b/packages/ui/src/components/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/frames/GridFrame.tsx b/packages/ui/src/components/GridFrame.tsx similarity index 100% rename from packages/ui/src/components/frames/GridFrame.tsx rename to packages/ui/src/components/GridFrame.tsx diff --git a/packages/ui/src/components/foundation/IconButton.tsx b/packages/ui/src/components/IconButton.tsx similarity index 100% rename from packages/ui/src/components/foundation/IconButton.tsx rename to packages/ui/src/components/IconButton.tsx diff --git a/packages/ui/src/components/foundation/IconGlyph.tsx b/packages/ui/src/components/IconGlyph.tsx similarity index 100% rename from packages/ui/src/components/foundation/IconGlyph.tsx rename to packages/ui/src/components/IconGlyph.tsx diff --git a/packages/ui/src/components/telemetry/LineChart.tsx b/packages/ui/src/components/LineChart.tsx similarity index 98% rename from packages/ui/src/components/telemetry/LineChart.tsx rename to packages/ui/src/components/LineChart.tsx index 0accdf7..386b515 100644 --- a/packages/ui/src/components/telemetry/LineChart.tsx +++ b/packages/ui/src/components/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/frames/ModuleCard.tsx b/packages/ui/src/components/ModuleCard.tsx similarity index 89% rename from packages/ui/src/components/frames/ModuleCard.tsx rename to packages/ui/src/components/ModuleCard.tsx index 9b96df9..d05bee0 100644 --- a/packages/ui/src/components/frames/ModuleCard.tsx +++ b/packages/ui/src/components/ModuleCard.tsx @@ -1,6 +1,6 @@ import { useId } from "react"; -import type { UiModuleBlock } from "../../types"; -import { IconGlyph } from "../foundation/IconGlyph"; +import type { UiModuleBlock } from "../types"; +import { IconGlyph } from "./IconGlyph"; export interface ModuleCardProps { module: UiModuleBlock; diff --git a/packages/ui/src/components/frames/Panel.tsx b/packages/ui/src/components/Panel.tsx similarity index 100% rename from packages/ui/src/components/frames/Panel.tsx rename to packages/ui/src/components/Panel.tsx diff --git a/packages/ui/src/components/foundation/ProgressMeter.tsx b/packages/ui/src/components/ProgressMeter.tsx similarity index 88% rename from packages/ui/src/components/foundation/ProgressMeter.tsx rename to packages/ui/src/components/ProgressMeter.tsx index c3f3aad..c7278bf 100644 --- a/packages/ui/src/components/foundation/ProgressMeter.tsx +++ b/packages/ui/src/components/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/frames/ScanlineField.tsx b/packages/ui/src/components/ScanlineField.tsx similarity index 100% rename from packages/ui/src/components/frames/ScanlineField.tsx rename to packages/ui/src/components/ScanlineField.tsx diff --git a/packages/ui/src/components/foundation/Separator.tsx b/packages/ui/src/components/Separator.tsx similarity index 100% rename from packages/ui/src/components/foundation/Separator.tsx rename to packages/ui/src/components/Separator.tsx diff --git a/packages/ui/src/components/operations/ServiceGroupPanel.tsx b/packages/ui/src/components/ServiceGroupPanel.tsx similarity index 76% rename from packages/ui/src/components/operations/ServiceGroupPanel.tsx rename to packages/ui/src/components/ServiceGroupPanel.tsx index 9b53340..4252cc9 100644 --- a/packages/ui/src/components/operations/ServiceGroupPanel.tsx +++ b/packages/ui/src/components/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/operations/ServicePanel.tsx b/packages/ui/src/components/ServicePanel.tsx similarity index 87% rename from packages/ui/src/components/operations/ServicePanel.tsx rename to packages/ui/src/components/ServicePanel.tsx index c8b85af..12a2e74 100644 --- a/packages/ui/src/components/operations/ServicePanel.tsx +++ b/packages/ui/src/components/ServicePanel.tsx @@ -1,5 +1,5 @@ -import type { UiServiceGroup } from "../../types"; -import { Panel } from "../frames/Panel"; +import type { UiServiceGroup } from "../types"; +import { Panel } from "./Panel"; import { ServiceRow } from "./ServiceRow"; import { StatusStrip } from "./StatusStrip"; diff --git a/packages/ui/src/components/operations/ServiceRow.tsx b/packages/ui/src/components/ServiceRow.tsx similarity index 89% rename from packages/ui/src/components/operations/ServiceRow.tsx rename to packages/ui/src/components/ServiceRow.tsx index e6e5aa9..cf065f4 100644 --- a/packages/ui/src/components/operations/ServiceRow.tsx +++ b/packages/ui/src/components/ServiceRow.tsx @@ -1,6 +1,6 @@ -import type { UiServiceRow } from "../../types"; -import { IconGlyph } from "../foundation/IconGlyph"; -import { StatusBadge } from "../foundation/StatusBadge"; +import type { UiServiceRow } from "../types"; +import { IconGlyph } from "./IconGlyph"; +import { StatusBadge } from "./StatusBadge"; export interface ServiceRowProps { service: UiServiceRow; diff --git a/packages/ui/src/components/telemetry/SignalTrace.tsx b/packages/ui/src/components/SignalTrace.tsx similarity index 100% rename from packages/ui/src/components/telemetry/SignalTrace.tsx rename to packages/ui/src/components/SignalTrace.tsx diff --git a/packages/ui/src/components/telemetry/Sparkline.tsx b/packages/ui/src/components/Sparkline.tsx similarity index 94% rename from packages/ui/src/components/telemetry/Sparkline.tsx rename to packages/ui/src/components/Sparkline.tsx index d03fa13..a79f532 100644 --- a/packages/ui/src/components/telemetry/Sparkline.tsx +++ b/packages/ui/src/components/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/foundation/StatusBadge.tsx b/packages/ui/src/components/StatusBadge.tsx similarity index 85% rename from packages/ui/src/components/foundation/StatusBadge.tsx rename to packages/ui/src/components/StatusBadge.tsx index d2b01b3..fb63a04 100644 --- a/packages/ui/src/components/foundation/StatusBadge.tsx +++ b/packages/ui/src/components/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/operations/StatusStrip.tsx b/packages/ui/src/components/StatusStrip.tsx similarity index 82% rename from packages/ui/src/components/operations/StatusStrip.tsx rename to packages/ui/src/components/StatusStrip.tsx index 3f2bca2..66c6451 100644 --- a/packages/ui/src/components/operations/StatusStrip.tsx +++ b/packages/ui/src/components/StatusStrip.tsx @@ -1,5 +1,5 @@ -import type { UiStatusItem } from "../../types"; -import { FooterCell } from "../frames/FooterCell"; +import type { UiStatusItem } from "../types"; +import { FooterCell } from "./FooterCell"; export interface StatusStripProps { id?: string; diff --git a/packages/ui/src/components/operations/SystemState.tsx b/packages/ui/src/components/SystemState.tsx similarity index 82% rename from packages/ui/src/components/operations/SystemState.tsx rename to packages/ui/src/components/SystemState.tsx index 438fa52..e817e2b 100644 --- a/packages/ui/src/components/operations/SystemState.tsx +++ b/packages/ui/src/components/SystemState.tsx @@ -1,5 +1,5 @@ -import type { UiSeverity } from "../../types"; -import { IconGlyph } from "../foundation/IconGlyph"; +import type { UiSeverity } from "../types"; +import { IconGlyph } from "./IconGlyph"; export interface SystemStateProps { title: string; diff --git a/packages/ui/src/components/telemetry/TelemetryCard.tsx b/packages/ui/src/components/TelemetryCard.tsx similarity index 91% rename from packages/ui/src/components/telemetry/TelemetryCard.tsx rename to packages/ui/src/components/TelemetryCard.tsx index 15481ee..97033c9 100644 --- a/packages/ui/src/components/telemetry/TelemetryCard.tsx +++ b/packages/ui/src/components/TelemetryCard.tsx @@ -1,7 +1,7 @@ import type { CSSProperties } from "react"; -import { clampPercent, formatMetricValue } from "../../format"; -import type { UiTelemetryCard } from "../../types"; -import { IconGlyph } from "../foundation/IconGlyph"; +import { clampPercent, formatMetricValue } from "../format"; +import type { UiTelemetryCard } from "../types"; +import { IconGlyph } from "./IconGlyph"; import { LineChart } from "./LineChart"; export interface TelemetryCardProps { diff --git a/packages/ui/src/components/telemetry/TelemetryGrid.tsx b/packages/ui/src/components/TelemetryGrid.tsx similarity index 94% rename from packages/ui/src/components/telemetry/TelemetryGrid.tsx rename to packages/ui/src/components/TelemetryGrid.tsx index 9f4d497..8afb9a2 100644 --- a/packages/ui/src/components/telemetry/TelemetryGrid.tsx +++ b/packages/ui/src/components/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/telemetry/TelemetryStrip.tsx b/packages/ui/src/components/TelemetryStrip.tsx similarity index 76% rename from packages/ui/src/components/telemetry/TelemetryStrip.tsx rename to packages/ui/src/components/TelemetryStrip.tsx index 35073bb..765c1e2 100644 --- a/packages/ui/src/components/telemetry/TelemetryStrip.tsx +++ b/packages/ui/src/components/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/components/foundation/ThemeToggle.tsx b/packages/ui/src/components/ThemeToggle.tsx similarity index 92% rename from packages/ui/src/components/foundation/ThemeToggle.tsx rename to packages/ui/src/components/ThemeToggle.tsx index 5d83031..9e883af 100644 --- a/packages/ui/src/components/foundation/ThemeToggle.tsx +++ b/packages/ui/src/components/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/operations/WeatherModule.tsx b/packages/ui/src/components/WeatherModule.tsx similarity index 53% rename from packages/ui/src/components/operations/WeatherModule.tsx rename to packages/ui/src/components/WeatherModule.tsx index c7e55db..0967cfb 100644 --- a/packages/ui/src/components/operations/WeatherModule.tsx +++ b/packages/ui/src/components/WeatherModule.tsx @@ -1,5 +1,5 @@ -import type { UiModuleBlock } from "../../types"; -import { ModuleCard } from "../frames/ModuleCard"; +import type { UiModuleBlock } from "../types"; +import { ModuleCard } from "./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 010f672..1cf40fe 100644 --- a/packages/ui/src/components/render.test.tsx +++ b/packages/ui/src/components/render.test.tsx @@ -1,16 +1,14 @@ import { renderToString } from "react-dom/server"; import { describe, expect, test } from "vitest"; -import { - Button, - DashboardFrame, - FooterCell, - IconButton, - ServiceRow, - StatusStrip, - TelemetryCard, - TelemetryGrid, - ThemeToggle, -} from "../index"; +import { Button } from "./Button"; +import { DashboardFrame } from "./DashboardFrame"; +import { FooterCell } from "./FooterCell"; +import { IconButton } from "./IconButton"; +import { ServiceRow } from "./ServiceRow"; +import { StatusStrip } from "./StatusStrip"; +import { TelemetryCard } from "./TelemetryCard"; +import { TelemetryGrid } from "./TelemetryGrid"; +import { ThemeToggle } from "./ThemeToggle"; import { dashboardPreviewFixtures } from "../fixtures"; describe("dashboard UI components", () => { diff --git a/packages/ui/src/content-boundary.test.ts b/packages/ui/src/content-boundary.test.ts index f7188a1..80b85ce 100644 --- a/packages/ui/src/content-boundary.test.ts +++ b/packages/ui/src/content-boundary.test.ts @@ -26,28 +26,15 @@ 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/frames/DashboardFrame.tsx")), - ).toBe(true); - expect( - existsSync(join(uiSourceRoot, "components/foundation/ThemeToggle.tsx")), - ).toBe(true); + expect(existsSync(join(uiSourceRoot, "components/DashboardFrame.tsx"))).toBe( + true, + ); + expect(existsSync(join(uiSourceRoot, "components/ThemeToggle.tsx"))).toBe( + true, + ); expect(existsSync(join(uiSourceRoot, "styles.css"))).toBe(true); }); - test("keeps 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 bc506c0..a9a83df 100644 --- a/packages/ui/src/index.ts +++ b/packages/ui/src/index.ts @@ -1,33 +1,33 @@ -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 { Badge } from "./components/Badge"; +export { Button } from "./components/Button"; +export { CornerBracketFrame } from "./components/CornerBracketFrame"; +export { DashboardHeader } from "./components/DashboardHeader"; +export { DashboardFrame } from "./components/DashboardFrame"; +export { DiagonalStripeField } from "./components/DiagonalStripeField"; +export { FooterCell } from "./components/FooterCell"; +export { FooterStatusCell } from "./components/FooterStatusCell"; +export { GridFrame } from "./components/GridFrame"; +export { IconGlyph } from "./components/IconGlyph"; +export { IconButton } from "./components/IconButton"; +export { LineChart } from "./components/LineChart"; +export { ModuleCard } from "./components/ModuleCard"; +export { Panel } from "./components/Panel"; +export { ProgressMeter } from "./components/ProgressMeter"; +export { Separator } from "./components/Separator"; +export { ServiceGroupPanel } from "./components/ServiceGroupPanel"; +export { ServicePanel } from "./components/ServicePanel"; +export { ServiceRow } from "./components/ServiceRow"; +export { ScanlineField } from "./components/ScanlineField"; +export { SignalTrace } from "./components/SignalTrace"; +export { Sparkline } from "./components/Sparkline"; +export { StatusBadge } from "./components/StatusBadge"; +export { StatusStrip } from "./components/StatusStrip"; +export { SystemState } from "./components/SystemState"; +export { TelemetryCard } from "./components/TelemetryCard"; +export { TelemetryGrid } from "./components/TelemetryGrid"; +export { TelemetryStrip } from "./components/TelemetryStrip"; +export { ThemeToggle } from "./components/ThemeToggle"; +export { WeatherModule } from "./components/WeatherModule"; export { dashboardPreviewFixtures } from "./fixtures"; export { getNextUiTheme, diff --git a/packages/ui/src/stories/Badge.stories.tsx b/packages/ui/src/stories/Badge.stories.tsx index 26e3ea8..d33394d 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 "../index"; +import { Badge } from "../components/Badge"; const meta = { title: "UI/Badge", diff --git a/packages/ui/src/stories/Button.stories.tsx b/packages/ui/src/stories/Button.stories.tsx index 084caa8..c207233 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 "../index"; +import { Button } from "../components/Button"; const meta = { title: "UI/Button", diff --git a/packages/ui/src/stories/CornerBracketFrame.stories.tsx b/packages/ui/src/stories/CornerBracketFrame.stories.tsx index d5c0110..dd7efbf 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 "../index"; +import { CornerBracketFrame } from "../components/CornerBracketFrame"; const meta = { title: "UI/CornerBracketFrame", diff --git a/packages/ui/src/stories/DashboardFrame.stories.tsx b/packages/ui/src/stories/DashboardFrame.stories.tsx index 894219e..e1963af 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 "../index"; +import { DashboardFrame } from "../components/DashboardFrame"; 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 c337158..8263dd1 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 "../index"; +import { DashboardHeader } from "../components/DashboardHeader"; 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 3bd800f..c44364b 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 "../index"; +import { DashboardFrame } from "../components/DashboardFrame"; 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 1f094ee..4ca01f2 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 "../index"; +import { DiagonalStripeField } from "../components/DiagonalStripeField"; const meta = { title: "UI/DiagonalStripeField", diff --git a/packages/ui/src/stories/FooterCell.stories.tsx b/packages/ui/src/stories/FooterCell.stories.tsx index 7aa06d8..3ba26c7 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 "../index"; +import { FooterCell } from "../components/FooterCell"; 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 bd985a1..8097ba3 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 "../index"; +import { FooterStatusCell } from "../components/FooterStatusCell"; 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 191bf6c..a43ad22 100644 --- a/packages/ui/src/stories/GridFrame.stories.tsx +++ b/packages/ui/src/stories/GridFrame.stories.tsx @@ -1,5 +1,6 @@ import type { Meta, StoryObj } from "@storybook/react-vite"; -import { GridFrame, ModuleCard } from "../index"; +import { GridFrame } from "../components/GridFrame"; +import { ModuleCard } from "../components/ModuleCard"; 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 612bf2a..35b58b9 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 "../index"; +import { IconButton } from "../components/IconButton"; const meta = { title: "UI/IconButton", diff --git a/packages/ui/src/stories/IconGlyph.stories.tsx b/packages/ui/src/stories/IconGlyph.stories.tsx index d832951..3f1715a 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 "../index"; +import { IconGlyph } from "../components/IconGlyph"; const meta = { title: "UI/IconGlyph", diff --git a/packages/ui/src/stories/LineChart.stories.tsx b/packages/ui/src/stories/LineChart.stories.tsx index ccbd502..bbf8690 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 "../index"; +import { LineChart } from "../components/LineChart"; const meta = { title: "UI/LineChart", diff --git a/packages/ui/src/stories/ModuleCard.stories.tsx b/packages/ui/src/stories/ModuleCard.stories.tsx index ea0305b..3366a55 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 "../index"; +import { ModuleCard } from "../components/ModuleCard"; 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 91eb61e..730c2d9 100644 --- a/packages/ui/src/stories/Panel.stories.tsx +++ b/packages/ui/src/stories/Panel.stories.tsx @@ -1,5 +1,6 @@ import type { Meta, StoryObj } from "@storybook/react-vite"; -import { Panel, ServiceRow } from "../index"; +import { Panel } from "../components/Panel"; +import { ServiceRow } from "../components/ServiceRow"; 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 eec35c2..d3bfdac 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 "../index"; +import { ProgressMeter } from "../components/ProgressMeter"; const meta = { title: "UI/ProgressMeter", diff --git a/packages/ui/src/stories/ScanlineField.stories.tsx b/packages/ui/src/stories/ScanlineField.stories.tsx index fbe53f4..89e554e 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 "../index"; +import { ScanlineField } from "../components/ScanlineField"; const meta = { title: "UI/ScanlineField", diff --git a/packages/ui/src/stories/Separator.stories.tsx b/packages/ui/src/stories/Separator.stories.tsx index 986ed23..e261f52 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 "../index"; +import { Separator } from "../components/Separator"; const meta = { title: "UI/Separator", diff --git a/packages/ui/src/stories/ServiceGroupPanel.stories.tsx b/packages/ui/src/stories/ServiceGroupPanel.stories.tsx index f5ebdce..7183771 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 "../index"; +import { ServiceGroupPanel } from "../components/ServiceGroupPanel"; 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 d29b7ad..750c428 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 "../index"; +import { ServicePanel } from "../components/ServicePanel"; 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 56ba076..9513d2c 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 "../index"; +import { ServiceRow } from "../components/ServiceRow"; 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 4089df8..ae8785b 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 "../index"; +import { SignalTrace } from "../components/SignalTrace"; const meta = { title: "UI/SignalTrace", diff --git a/packages/ui/src/stories/Sparkline.stories.tsx b/packages/ui/src/stories/Sparkline.stories.tsx index af3e6e5..efecfbd 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 "../index"; +import { Sparkline } from "../components/Sparkline"; const meta = { title: "UI/Sparkline", diff --git a/packages/ui/src/stories/StatusBadge.stories.tsx b/packages/ui/src/stories/StatusBadge.stories.tsx index 92a444d..4513a08 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 "../index"; +import { StatusBadge } from "../components/StatusBadge"; const meta = { title: "UI/StatusBadge", diff --git a/packages/ui/src/stories/StatusStrip.stories.tsx b/packages/ui/src/stories/StatusStrip.stories.tsx index 2fc236d..7b808fa 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 "../index"; +import { StatusStrip } from "../components/StatusStrip"; 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 9b3f6e5..585540e 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 "../index"; +import { SystemState } from "../components/SystemState"; const meta = { title: "UI/SystemState", diff --git a/packages/ui/src/stories/TelemetryCard.stories.tsx b/packages/ui/src/stories/TelemetryCard.stories.tsx index 3ac0e1e..dbdf301 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 "../index"; +import { TelemetryCard } from "../components/TelemetryCard"; 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 34493c5..6b0166e 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 "../index"; +import { TelemetryGrid } from "../components/TelemetryGrid"; 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 f7c9d13..76fc983 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 "../index"; +import { TelemetryStrip } from "../components/TelemetryStrip"; 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 f2edeea..33e494a 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 "../index"; +import { ThemeToggle } from "../components/ThemeToggle"; const meta = { title: "UI/ThemeToggle", diff --git a/packages/ui/src/stories/WeatherModule.stories.tsx b/packages/ui/src/stories/WeatherModule.stories.tsx index a376448..6429de2 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 "../index"; +import { WeatherModule } from "../components/WeatherModule"; import { moduleBlocks } from "./story-data"; const meta = { diff --git a/packages/ui/src/storybook.test.ts b/packages/ui/src/storybook.test.ts index 83f51c7..4ef14ba 100644 --- a/packages/ui/src/storybook.test.ts +++ b/packages/ui/src/storybook.test.ts @@ -1,5 +1,5 @@ import { existsSync, readdirSync, readFileSync } from "node:fs"; -import { basename, join, relative } from "node:path"; +import { join } from "node:path"; import { describe, expect, test } from "vitest"; const root = process.cwd(); @@ -9,13 +9,39 @@ const packageRoot = existsSync(join(root, "packages/ui/package.json")) const componentsDir = join(packageRoot, "src/components"); const storiesDir = join(packageRoot, "src/stories"); -const allowedComponentDomains = [ - "foundation", - "frames", - "operations", - "telemetry", +const requiredStoryFiles = [ + "Badge.stories.tsx", + "Button.stories.tsx", + "DashboardFrame.stories.tsx", + "DashboardHeader.stories.tsx", + "DashboardOnePager.stories.tsx", + "CornerBracketFrame.stories.tsx", + "DiagonalStripeField.stories.tsx", + "FooterCell.stories.tsx", + "FooterStatusCell.stories.tsx", + "GridFrame.stories.tsx", + "IconButton.stories.tsx", + "IconGlyph.stories.tsx", + "LineChart.stories.tsx", + "ModuleCard.stories.tsx", + "Panel.stories.tsx", + "ProgressMeter.stories.tsx", + "Separator.stories.tsx", + "ServiceGroupPanel.stories.tsx", + "ServicePanel.stories.tsx", + "ServiceRow.stories.tsx", + "SignalTrace.stories.tsx", + "Sparkline.stories.tsx", + "StatusBadge.stories.tsx", + "StatusStrip.stories.tsx", + "SystemState.stories.tsx", + "ScanlineField.stories.tsx", + "TelemetryCard.stories.tsx", + "TelemetryGrid.stories.tsx", + "TelemetryStrip.stories.tsx", + "ThemeToggle.stories.tsx", + "WeatherModule.stories.tsx", ] as const; -const compositionStoryFiles = ["DashboardOnePager.stories.tsx"] as const; const forbiddenStoryContent = [ "dimension lab", @@ -36,26 +62,17 @@ describe("Storybook inventory", () => { }); 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); + for (const filename of requiredStoryFiles) { + expect(existsSync(join(storiesDir, filename)), `${filename} 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"); + const dashboardFrame = readFileSync( + join(componentsDir, "DashboardFrame.tsx"), + "utf8", + ); expect(packageStyles).toContain('./components/styles.css'); expect(dashboardFrame).not.toContain('./styles.css'); @@ -70,33 +87,12 @@ describe("Storybook inventory", () => { }); test("keeps component and story files paired as the UI inventory changes", () => { - const componentStoryFiles = new Set( - componentInventory().map((component) => component.storyFile), - ); + const componentStoryFiles = readdirSync(componentsDir) + .filter((filename) => filename.endsWith(".tsx") && !filename.endsWith(".test.tsx")) + .map((filename) => filename.replace(".tsx", ".stories.tsx")); 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}";`, - ); + expect(existsSync(join(storiesDir, filename)), `${filename} is missing`).toBe(true); } }); @@ -118,7 +114,9 @@ describe("Storybook inventory", () => { "ScanlineField", "SignalTrace", ]) { - expect(existsSync(requiredComponentPath(component)), `${component} is missing`).toBe(true); + expect(existsSync(join(componentsDir, `${component}.tsx`)), `${component} is missing`).toBe( + true, + ); expect( existsSync(join(storiesDir, `${component}.stories.tsx`)), `${component}.stories.tsx is missing`, @@ -133,19 +131,14 @@ describe("Storybook inventory", () => { "ScanlineField", "SignalTrace", ]) { - const source = readFileSync(requiredComponentPath(component), "utf8"); + const source = readFileSync(join(componentsDir, `${component}.tsx`), "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); } @@ -160,71 +153,3 @@ describe("Storybook inventory", () => { 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 { - name: string; - path: string; - relativeExportPath: string; - storyFile: 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 relativeExportPath = `./${relative(join(packageRoot, "src"), entryPath) - .replace(/\\/g, "/") - .replace(/\.tsx$/, "")}`; - - return [ - { - name, - path: entryPath, - relativeExportPath, - storyFile: `${name}.stories.tsx`, - }, - ]; - }); -} - -function storyFiles(): string[] { - return readdirSync(storiesDir) - .filter((filename) => filename.endsWith(".stories.tsx")) - .sort(); -}