Files
design-system/docs/superpowers/specs/2026-03-18-design-system-design.md
hsiegeln cce16d25c6 Fix remaining spec review flags
- EventFeed severity uses 'running' not 'info' for consistency
- Fix stale SHA reference in design decisions table
- Document StatusDot live/stale/dead token mappings
- Type Tag color prop as semantic union instead of string

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 08:44:49 +01:00

24 KiB

Cameleer3 Design System — Specification

Date: 2026-03-18 Status: Draft Scope: Standalone React design system with full component library, theming, and page layouts


1. Overview

Build a React-based design system for the Cameleer3 operations dashboard. The system follows a shadcn/ui-like approach: all components are owned by the project, composable, and have no external UI library dependencies. Components are styled with CSS Modules + CSS custom properties, supporting light and dark themes.

The design language is derived from existing HTML mockups (ui-mocks/) and the live application's user management UI. Key characteristics:

  • Warm parchment surfaces (not cold white/gray)
  • Warm charcoal sidebar
  • Amber-gold brand accent
  • DM Sans body + JetBrains Mono data typography
  • Warm status colors (olive green, burnt orange, terracotta, teal)
  • Deterministic badge/avatar colors derived from name hashing

Goals

  • Standalone layouts that work without a server (mock data)
  • Light theme first, dark theme support built in from the start
  • Reusable primitives that compose into dashboard pages
  • Visual parity with existing mockups and live application

Non-Goals

  • Server integration, API calls, real-time data
  • Authentication / authorization flows
  • Mobile responsiveness (dashboard targets 1920px desktop)

2. Tech Stack

Concern Choice
Framework React 18+ with TypeScript
Build tool Vite
Routing React Router v6
Styling CSS Modules + CSS custom properties
Fonts DM Sans (body), JetBrains Mono (data)
State React Context (theme, sidebar)
Package manager npm

No external component libraries (no MUI, Chakra, Radix, etc.). All components built from scratch.


3. Theming Architecture

3.1 Design Tokens

All visual properties are defined as CSS custom properties in tokens.css. Two rule blocks:

  • :root — light theme (default)
  • [data-theme="dark"] — dark theme overrides

Token categories:

Surface:    --bg-body, --bg-surface, --bg-raised, --bg-inset, --bg-hover
Sidebar:    --sidebar-bg, --sidebar-hover, --sidebar-active, --sidebar-text, --sidebar-muted
Text:       --text-primary, --text-secondary, --text-muted, --text-faint
Borders:    --border, --border-subtle
Brand:      --amber, --amber-light, --amber-bg, --amber-deep
Status:     --success, --success-bg, --success-border
            --warning, --warning-bg, --warning-border
            --error, --error-bg, --error-border
            --running, --running-bg, --running-border
Typography: --font-body, --font-mono
Spacing:    --radius-sm (5px), --radius-md (8px), --radius-lg (12px)
Shadows:    --shadow-sm, --shadow-md, --shadow-lg, --shadow-card
Chart:      --chart-1 through --chart-8 (amber, olive, teal, burnt orange,
            deep amber, sage, terracotta, gold) — used for multi-series
            charts and sparklines

Semantic color naming convention: Components use semantic variant names that map to tokens:

  • primary--amber tokens
  • success--success tokens
  • warning--warning tokens
  • error--error tokens
  • running--running tokens (teal, for in-progress/active states)

This convention applies to all components with color/accent props (Card, Badge, StatusDot, FilterPill, InfoCallout, etc.). No mixing of token-level names (teal, green) with semantic names.

Light theme values are taken directly from mock-v2-light.html :root block.

Dark theme strategy: The existing mock-v2-dark.html is considered "too colorful" and will not be used as-is. The dark theme will be a muted redesign that:

  • Reuses the same token names as light (no separate --bg-deep / --bg-base)
  • Inverts lightness and reduces saturation
  • Drops glow tokens (--amber-glow, --success-glow, etc.) — these are not canonical
  • Uses the same --radius-sm: 5px value (the dark mock's 6px was unintentional)
  • All dark overrides go in a single [data-theme="dark"] block remapping the same variable names

3.2 Deterministic Color Hashing

A hashColor(name: string) utility generates consistent HSL colors from any string:

  1. Compute FNV-1a hash of the input name (synchronous, fast, good distribution)
  2. Take hash modulo 360 → hue angle
  3. Light theme: hsl(hue, 45%, 92%) background, hsl(hue, 55%, 35%) text
  4. Dark theme: hsl(hue, 35%, 20%) background, hsl(hue, 45%, 75%) text

FNV-1a is chosen over SHA-256 because it is synchronous (no crypto.subtle async), lightweight (~10 lines), and provides sufficient distribution for color bucketing.

Used by Badge and Avatar components when no explicit color variant is provided.

3.3 Theme Switching

  • ThemeProvider React context stores 'light' | 'dark'
  • Persisted to localStorage
  • Toggling sets document.documentElement.dataset.theme
  • All components reference tokens — no component-level theme logic needed

4. Project Structure

src/
├── design-system/
│   ├── tokens.css                  # All CSS custom properties
│   ├── reset.css                   # Box-sizing, scrollbar, base styles
│   ├── utils/
│   │   └── hashColor.ts            # Name → HSL deterministic color
│   ├── primitives/
│   │   ├── Avatar/
│   │   │   ├── Avatar.tsx
│   │   │   └── Avatar.module.css
│   │   ├── Badge/
│   │   ├── Button/
│   │   ├── Card/
│   │   ├── Checkbox/
│   │   ├── CodeBlock/
│   │   ├── Collapsible/
│   │   ├── DateTimePicker/
│   │   ├── DateRangePicker/
│   │   ├── EmptyState/
│   │   ├── FilterPill/
│   │   ├── InfoCallout/
│   │   ├── Input/
│   │   ├── KeyboardHint/
│   │   ├── MonoText/
│   │   ├── Select/
│   │   ├── SectionHeader/
│   │   ├── Sparkline/
│   │   ├── Spinner/
│   │   ├── StatCard/
│   │   ├── StatusDot/
│   │   ├── Tag/
│   │   ├── Toggle/
│   │   ├── Tooltip/
│   │   └── index.ts                # Barrel export
│   ├── composites/
│   │   ├── AreaChart/
│   │   ├── BarChart/
│   │   ├── Breadcrumb/
│   │   ├── CommandPalette/
│   │   ├── DataTable/
│   │   ├── DetailPanel/
│   │   ├── Dropdown/
│   │   ├── EventFeed/
│   │   ├── FilterBar/
│   │   ├── LineChart/
│   │   ├── MenuItem/
│   │   ├── Modal/
│   │   ├── ProcessorTimeline/
│   │   ├── ShortcutsBar/
│   │   ├── Tabs/
│   │   └── index.ts
│   ├── layout/
│   │   ├── AppShell/
│   │   ├── Sidebar/
│   │   ├── TopBar/
│   │   └── index.ts
│   └── providers/
│       └── ThemeProvider.tsx
├── pages/
│   ├── Dashboard/
│   ├── Metrics/
│   ├── RouteDetail/
│   ├── ExchangeDetail/
│   └── AgentHealth/
├── mocks/                          # Static TypeScript mock data
│   ├── executions.ts
│   ├── routes.ts
│   ├── agents.ts
│   └── metrics.ts
├── App.tsx                         # Router setup
├── main.tsx                        # Entry point
└── index.css                       # Imports tokens.css + reset.css

Naming Conventions

  • Components: PascalCase directories with .tsx + .module.css
  • Tokens/utilities: camelCase
  • Barrel exports from each layer (primitives/index.ts, composites/index.ts, layout/index.ts)

5. Component Specifications

5.1 Foundations

tokens.css

All CSS custom properties for both themes. Light values sourced from mock-v2-light.html. See Section 3.1 for full token list.

reset.css

  • Box-sizing border-box
  • Custom scrollbar (6px, warm tones)
  • Base font: DM Sans 14px, line-height 1.5
  • Anti-aliased rendering
  • html { font-size: 14px; }

hashColor.ts

  • hashColor(name: string): { bg: string; text: string; border: string }
  • Returns CSS color strings appropriate for current theme
  • Uses a synchronous hash (FNV-1a) for hue mapping — SHA-256 is asynchronous in browsers and overkill for color bucketing (see Section 3.2)

5.2 Primitives

Avatar

  • Circular element with initials (1-2 chars extracted from name)
  • Color derived from hashColor(name)
  • Sizes: sm (24px), md (28px), lg (40px)
  • Props: name: string, size?: 'sm' | 'md' | 'lg'

Badge

  • Inline label pill
  • Variants: filled (solid bg + text from hash), outlined (transparent bg, colored border), dashed (dashed border, for inherited/transitive items)
  • Color: auto from hashColor(label) or explicit semantic color for status badges
  • Props: label: string, variant?: 'filled' | 'outlined' | 'dashed', color?: 'success' | 'warning' | 'error' | 'running' | 'auto'
  • Optional onRemove for dismissible badges

Button

  • Variants: primary (amber fill), secondary (outline), danger (red outline/fill), ghost (no border)
  • Sizes: sm, md
  • Props: standard button props + variant, size, loading?: boolean

Card

  • White surface container
  • Subtle border (--border-subtle) + shadow (--shadow-card)
  • Border radius --radius-md
  • Optional top accent stripe (colored 2px bar)
  • Props: accent?: 'primary' | 'success' | 'error' | 'running' | 'warning', children

Checkbox

  • Styled checkbox with warm focus ring
  • Props: standard input[checkbox] props + label?: string

DateTimePicker

  • Initial implementation: styled native <input type="datetime-local"> matching the Input component's warm styling (focus ring, border, background)
  • Custom calendar dropdown is a future enhancement
  • Props: value: string, onChange, min?: string, max?: string

DateRangePicker

  • Start + end datetime selection using two DateTimePicker inputs
  • Built-in presets bar: "Last 1h", "Last 6h", "Today", "This shift", "Last 24h", "Last 7d", "Custom"
  • Presets are FilterPill-style chips above the inputs; clicking a preset auto-fills both values
  • Props: value: { start: Date; end: Date }, onChange, presets?: Preset[]

EmptyState

  • Centered placeholder for empty lists/tables
  • Icon slot + title + description
  • Props: icon?: ReactNode, title: string, description?: string, action?: ReactNode

FilterPill

  • Selectable pill with optional color dot + count
  • States: default, hover, active (amber bg), active-colored (status-specific bg)
  • Props: label: string, count?: number, active?: boolean, color?: 'success' | 'error' | 'running', onClick

InfoCallout

  • Block with left accent border (3px, amber by default)
  • Light background
  • Props: children, variant?: 'running' | 'warning' | 'error'

Input

  • Text input with warm focus ring (--amber border, --amber-bg shadow)
  • Optional left icon slot
  • Props: standard input props + icon?: ReactNode

KeyboardHint

  • Styled <kbd> element
  • Mono font, subtle border + background
  • Props: keys: string (e.g., "Ctrl+K")

MonoText

  • Span wrapper with JetBrains Mono font
  • Props: children, size?: 'xs' | 'sm' | 'md'

SectionHeader

  • Uppercase, small, muted label with letter-spacing
  • Props: children, action?: ReactNode (optional right-aligned element like "+ Add")

Select

  • Styled dropdown select matching Input styling
  • Warm focus ring
  • Props: standard select props + options: Option[]

Spinner

  • CSS-only loading indicator
  • Amber colored
  • Sizes: sm (16px), md (24px), lg (32px)

StatCard

  • KPI display card extending Card
  • Top accent stripe, label, large mono value, trend indicator, detail line
  • Props: label: string, value: string | number, unit?: string, trend?: { direction: 'up' | 'down' | 'flat'; value: string; sentiment: 'good' | 'bad' | 'neutral' }, detail?: ReactNode, accent?: 'primary' | 'success' | 'error' | 'running' | 'warning', sparkline?: number[]

StatusDot

  • 6-7px colored circle
  • Semantic variants (map to status tokens): success (green), warning (orange), error (red), running (teal)
  • Agent-specific variants (map to tokens): live--success + pulse, stale--warning, dead--text-muted
  • Optional pulse animation (enabled by default for live)
  • Props: variant: 'live' | 'stale' | 'dead' | 'success' | 'warning' | 'error' | 'running', pulse?: boolean

Tag

  • Removable pill with x dismiss button
  • Color from hashColor(label) by default, or explicit semantic color
  • Props: label: string, onRemove?: () => void, color?: 'success' | 'warning' | 'error' | 'running' | 'auto'

Toggle

  • Styled on/off switch
  • Props: standard input[checkbox] props + label?: string

Tooltip

  • Hover-triggered info popup
  • Positions: top, bottom, left, right
  • Props: content: ReactNode, position?: 'top' | 'bottom' | 'left' | 'right', children (trigger element)

CodeBlock

  • Monospace preformatted text display for JSON, XML, stack traces
  • Inset background (--bg-inset), rounded corners
  • Optional line numbers (left gutter)
  • Optional copy-to-clipboard button (top-right)
  • Auto-detects and pretty-prints JSON
  • Match highlighting support (bold amber for search matches)
  • Props: content: string, language?: 'json' | 'xml' | 'text', lineNumbers?: boolean, copyable?: boolean, highlights?: string[], maxHeight?: string

Collapsible

  • Expandable/collapsible section with toggle trigger
  • Animated height transition
  • Props: open?: boolean, onToggle?: () => void, title: ReactNode, children, defaultOpen?: boolean

Sparkline

  • Inline SVG mini-chart (polyline)
  • Renders a small trend line from an array of numbers
  • Sizes: fits within its container (typical: 60x20px in table cells, 80x24px in stat cards)
  • Color inherits from parent or explicit prop
  • Props: data: number[], color?: string, width?: number, height?: number, strokeWidth?: number

5.3 Composites

Breadcrumb

  • Path segments with / separators
  • Last segment is active (bold, primary color)
  • Props: items: { label: string; href?: string }[]

CommandPalette

  • Full-screen overlay modal triggered by Ctrl+K
  • Search input with scoped filter tags (removable, amber-styled)
  • Category tabs with counts (All, Executions, Routes, Exchanges, Agents)
  • Grouped results by category with section headers
  • Result items: icon + title + badges + meta + timestamp
  • Inline expandable detail (JSON preview with match highlighting)
  • Match highlighting (bold amber) in result text
  • Keyboard navigation: arrows to navigate, enter to open, tab to filter, esc to close
  • Bottom bar with keyboard shortcut hints
  • Props: open: boolean, onClose, onSelect: (result: SearchResult) => void, data: SearchResult[]
  • SearchResult interface:
    type SearchCategory = 'execution' | 'route' | 'exchange' | 'agent'
    interface SearchResult {
      id: string
      category: SearchCategory
      title: string            // e.g., route name "content-based-routing"
      badges: Badge[]          // status badges (Completed, Failed, etc.)
      meta: string             // correlation ID, duration, context line
      timestamp?: string       // relative time ("2s ago")
      icon?: ReactNode
      expandedContent?: string // JSON preview for inline expansion
      matchRanges?: [number, number][] // highlight ranges in title/meta
    }
    
  • Scoped filter tags narrow the search by category or field (e.g., route: order filters to routes matching "order"); removing a tag widens the search back

DataTable

  • Sortable columns (click header to sort, indicator arrow)
  • Compact 40px rows
  • Row selection (single-click to select, highlights row)
  • Optional left accent border on rows (e.g., red for failed)
  • Inline content below row (e.g., error preview)
  • Column types: text, mono, badge, status, duration, timestamp
  • Client-side pagination with configurable page size (default 25, options: 10/25/50/100)
  • Pagination bar at bottom: page info ("1-25 of 3,241"), page size selector, prev/next buttons
  • Props: columns: Column[], data: T[], onRowClick?, selectedId?, sortable?: boolean, pageSize?: number, pageSizeOptions?: number[]

DetailPanel

  • Sliding panel from the right (400px width)
  • Header with close button
  • Tabbed content (Overview, Processors, Exchange, Error)
  • Bottom action bar
  • Animated slide-in/out
  • Props: open: boolean, onClose, title: string, tabs: Tab[], actions?: ReactNode

Dropdown

  • Trigger + floating menu
  • Menu items with optional icons, dividers
  • Props: trigger: ReactNode, items: DropdownItem[]

FilterBar

  • Composable row: search input + filter pills + separator + time presets
  • Active filter tags row below (removable tags + "Clear all")
  • Props: filters: Filter[], activeFilters: ActiveFilter[], onFilterChange, searchPlaceholder?: string

MenuItem

  • Sidebar navigation item
  • Health dot + label + meta text + count badge
  • States: default, hover, active (amber accent + left border)
  • Indentation support for tree hierarchy
  • Props: label: string, meta?: string, count?: number, health?: StatusDot variant, active?: boolean, indent?: number

Modal

  • Generic overlay dialog
  • Backdrop + centered content card
  • Close on esc, close on backdrop click
  • Props: open: boolean, onClose, title?: string, children, size?: 'sm' | 'md' | 'lg'

Tabs

  • Horizontal tab bar
  • Optional count badges per tab
  • Underline active indicator
  • Props: tabs: { label: string; count?: number; value: string }[], active: string, onChange

AreaChart / LineChart

  • SVG-based time-series charts, no external charting libraries
  • Features: grid lines, axis labels, optional SLA threshold line (dashed, labeled), legend
  • Multiple series support using --chart-1 through --chart-8 tokens
  • Hover tooltip showing values at cursor position
  • AreaChart fills below the line; LineChart is lines only
  • Props: series: { label: string; data: { x: number | Date; y: number }[]; color?: string }[], xLabel?: string, yLabel?: string, threshold?: { value: number; label: string }, height?: number

BarChart

  • SVG vertical bar chart for categorical or time-bucketed data
  • Stacked or grouped mode
  • Same token-based coloring as AreaChart/LineChart
  • Props: series: { label: string; data: { x: string; y: number }[]; color?: string }[], stacked?: boolean, height?: number

ProcessorTimeline

  • Gantt-style horizontal bar timeline for exchange processor steps
  • Each bar: processor name (left label), colored bar (green=ok, amber=slow, red=fail), duration label (right)
  • Bar width proportional to duration relative to total execution time
  • Clickable bars (select processor)
  • Props: processors: { name: string; type: string; durationMs: number; status: 'ok' | 'slow' | 'fail'; startMs: number }[], totalMs: number, onProcessorClick?

EventFeed

  • Scrolling list of real-time events
  • Each item: icon (by severity), text content, relative timestamp
  • Auto-scrolls to newest (with "pause" on manual scroll)
  • Category filtering (error, warning, info, success)
  • Severity uses 'error' | 'warning' | 'success' | 'running' to stay consistent with the semantic naming convention. In event context, running represents informational/neutral events (teal).
  • Props: events: { id: string; severity: 'error' | 'warning' | 'success' | 'running'; message: string; timestamp: Date }[], maxItems?: number

ShortcutsBar

  • Fixed-position bar at bottom-right of viewport
  • Displays keyboard shortcut hints as a horizontal list
  • Each hint: KeyboardHint + description text
  • Props: shortcuts: { keys: string; label: string }[]

5.4 Layout

AppShell

  • 3-column flexbox layout: Sidebar | Main | DetailPanel
  • Full viewport height (100vh), no body scroll
  • Detail panel conditionally rendered (slides in/out)
  • Props: sidebar: ReactNode, children (main content), detail?: ReactNode

Sidebar

  • 220px fixed width, warm charcoal (--sidebar-bg)
  • Sections from top to bottom:
    1. Logo area: camel icon + "cameleer" brand (JetBrains Mono, amber-light) + version
    2. Search input (filter apps)
    3. Navigation section: "Applications" header + MenuItem list
    4. Divider
    5. Routes section: "Routes" header + indented MenuItem list
    6. Agent health section: header with badge ("4/4 live") + agent items (dot, name, version, tps, last-seen)
    7. Bottom: Admin, API Docs links
  • Props: apps: App[], routes: Route[], agents: Agent[], activeItem?: string

TopBar

  • 48px height, white background, bottom border
  • Left: Breadcrumb
  • Center: Global search trigger (click opens CommandPalette)
  • Right: Environment badge, shift indicator, user avatar + name
  • Props: breadcrumb: BreadcrumbItem[], environment?: string, shift?: string, user?: { name: string }

6. Pages

Pages compose design system components with mock data. Each page is a route.

Route Page Description
/ Dashboard Main operations view from mock-v2-light.html
/metrics Metrics KPI dashboard from mock-v3-metrics-dashboard
/routes/:id RouteDetail Per-route stats from mock-v3-route-detail
/exchanges/:id ExchangeDetail Message inspector from mock-v3-exchange-detail
/agents AgentHealth Agent monitoring from mock-v3-agent-health

Pages are built after the design system layer is complete.


7. Mock Data

Static TypeScript objects in src/mocks/ providing realistic data matching the HTML mockups:

  • executions.ts — execution rows with status, duration, order IDs, error messages
  • routes.ts — route definitions with processor lists
  • agents.ts — agent health data (name, version, status, tps, last-seen)
  • metrics.ts — KPI values, chart data points

All data is imported directly by pages — no fetch calls, no loading states needed for standalone mode.


8. Implementation Order

  1. Project scaffold — Vite + React + TypeScript + React Router + CSS Modules config
  2. Foundationstokens.css, reset.css, hashColor.ts, ThemeProvider
  3. Primitives — all primitive components (can be built in parallel)
  4. Composites — DataTable, CommandPalette, FilterBar, DetailPanel, etc.
  5. Layout — AppShell, Sidebar, TopBar
  6. Mock data — static data files
  7. Pages — Dashboard first, then remaining pages

9. Design Decisions

Decision Rationale
CSS Modules over Tailwind Mocks already define a CSS variable token system; CSS Modules preserve that naturally and scope class names
No component library dependency shadcn/ui-like ownership — components are part of the codebase, not an npm package
Light theme first User preference; dark theme is "too colorful" in current mocks and needs a muted redesign
Deterministic badge colors Existing app uses hash-based name→color mapping; design system replicates this with FNV-1a
3-column layout as shell All pages share sidebar + topbar; only main content and optional detail panel change per route
Mock data as TypeScript objects Enables standalone layouts without server; type-safe; easy to replace with API calls later
DM Sans + JetBrains Mono Established in existing mocks and live application