- 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>
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→--ambertokenssuccess→--successtokenswarning→--warningtokenserror→--errortokensrunning→--runningtokens (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: 5pxvalue (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:
- Compute FNV-1a hash of the input name (synchronous, fast, good distribution)
- Take hash modulo 360 → hue angle
- Light theme:
hsl(hue, 45%, 92%)background,hsl(hue, 55%, 35%)text - 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
ThemeProviderReact 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
onRemovefor 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 (
--amberborder,--amber-bgshadow) - 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[] SearchResultinterface: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: orderfilters 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-1through--chart-8tokens - 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,runningrepresents 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:
- Logo area: camel icon + "cameleer" brand (JetBrains Mono, amber-light) + version
- Search input (filter apps)
- Navigation section: "Applications" header + MenuItem list
- Divider
- Routes section: "Routes" header + indented MenuItem list
- Agent health section: header with badge ("4/4 live") + agent items (dot, name, version, tps, last-seen)
- 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 messagesroutes.ts— route definitions with processor listsagents.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
- Project scaffold — Vite + React + TypeScript + React Router + CSS Modules config
- Foundations —
tokens.css,reset.css,hashColor.ts,ThemeProvider - Primitives — all primitive components (can be built in parallel)
- Composites — DataTable, CommandPalette, FilterBar, DetailPanel, etc.
- Layout — AppShell, Sidebar, TopBar
- Mock data — static data files
- 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 |