diff --git a/docs/superpowers/specs/2026-03-24-mock-deviations-design.md b/docs/superpowers/specs/2026-03-24-mock-deviations-design.md
new file mode 100644
index 0000000..53ca0c4
--- /dev/null
+++ b/docs/superpowers/specs/2026-03-24-mock-deviations-design.md
@@ -0,0 +1,202 @@
+# Mock UI Deviations — Design Spec
+
+## Overview
+
+The mock pages in `src/pages/` build several UI patterns using raw CSS and inline HTML that should either be promoted into the design system or refactored to use existing components. This spec captures each deviation and its resolution to minimize rework when transitioning to the real application.
+
+## Decision Framework
+
+A pattern is promoted to the design system when it:
+- Appears on 2+ pages with the same structure
+- Is visually distinctive and would be inconsistent if reimplemented
+- Will be needed by the real application
+
+A pattern stays in the pages when it is page-specific composition or a one-off layout.
+
+---
+
+## 1. KpiStrip — New Composite
+
+**Problem:** Dashboard, Routes, and AgentHealth each build a custom KPI header strip (~320 lines of duplicated layout code). Same visual structure: horizontal row of cards with colored left border, uppercase label, large value, trend indicator, subtitle, and optional sparkline.
+
+**Solution:** New composite `KpiStrip`.
+
+```tsx
+interface KpiItem {
+ label: string
+ value: string | number
+ trend?: { direction: 'up' | 'down' | 'flat'; label: string }
+ subtitle?: string
+ sparkline?: number[]
+ borderColor?: string // CSS token, e.g. "var(--success)"
+}
+
+interface KpiStripProps {
+ items: KpiItem[]
+ className?: string
+}
+```
+
+**Layout:**
+- Horizontal flex row with equal-width cards
+- Each card: 3px left border (colored via `borderColor`, default `var(--amber)`), padding 16px 20px
+- Card surface: `var(--bg-surface)`, border: `var(--border-subtle)`, radius: `var(--radius-md)`
+- Label: 11px uppercase, monospace weight 500, `var(--text-muted)`
+- Value: 28px, weight 700, `var(--text-primary)`
+- Trend: inline next to value, 11px, colored by direction (up+bad=error, up+neutral=muted, down=success — but color is caller's responsibility via `trend.label` content; the component just renders the text)
+- Subtitle: 11px, `var(--text-secondary)`
+- Sparkline: existing `Sparkline` primitive rendered top-right of card
+
+**File location:** `src/design-system/composites/KpiStrip/`
+
+**Pages to refactor:** Dashboard.tsx, Routes.tsx, AgentHealth.tsx — replace inline `KpiHeader` functions with ``.
+
+---
+
+## 2. SplitPane — New Composite
+
+**Problem:** Admin RBAC tabs (UsersTab, GroupsTab, RolesTab) each build a custom CSS grid split-pane layout with scrollable list, detail panel, and empty state placeholder.
+
+**Solution:** New composite `SplitPane`.
+
+```tsx
+interface SplitPaneProps {
+ list: ReactNode
+ detail: ReactNode | null // null renders empty state
+ emptyMessage?: string // Default: "Select an item to view details"
+ ratio?: '1:1' | '1:2' | '2:3' // Default: '1:2'
+ className?: string
+}
+```
+
+**Layout:**
+- CSS grid with two columns at the specified ratio
+- Left panel: scrollable, `var(--bg-surface)` background, right border `var(--border-subtle)`
+- Right panel: scrollable, `var(--bg-raised)` background
+- Empty state: centered text, `var(--text-muted)`, italic
+- Both panels fill available height (the parent controls the overall height)
+
+**File location:** `src/design-system/composites/SplitPane/`
+
+**Pages to refactor:** UsersTab.tsx, GroupsTab.tsx, RolesTab.tsx — replace custom grid CSS with ``.
+
+---
+
+## 3. Refactor AgentHealth Instance Table to DataTable
+
+**Problem:** AgentHealth builds instance tables using raw HTML `` elements instead of the existing `DataTable` composite.
+
+**Solution:** Refactor to use `DataTable` with column definitions and custom cell renderers. No design system changes needed.
+
+**Refactor scope:**
+- Replace `` blocks in AgentHealth.tsx (~60 lines) with `` using `flush` prop
+- Define columns with `render` functions for State (Badge) and StatusDot columns
+- Remove associated table CSS from AgentHealth.module.css
+
+---
+
+## 4. LogViewer — New Composite
+
+**Problem:** AgentInstance renders log entries as custom HTML with inline styling — timestamped lines with severity levels in monospace.
+
+**Solution:** New composite `LogViewer`.
+
+```tsx
+interface LogEntry {
+ timestamp: string
+ level: 'info' | 'warn' | 'error' | 'debug'
+ message: string
+}
+
+interface LogViewerProps {
+ entries: LogEntry[]
+ maxHeight?: number | string // Default: 400
+ className?: string
+}
+```
+
+**Layout:**
+- Scrollable container with `max-height`, `var(--bg-inset)` background, `var(--radius-md)` border-radius
+- Each line: flex row with timestamp (muted, monospace, 11px) + level badge + message (monospace, 12px)
+- Level badge colors: info=`var(--running)`, warn=`var(--warning)`, error=`var(--error)`, debug=`var(--text-muted)`
+- Level badge: uppercase, 9px, `var(--font-mono)`, pill-shaped with tinted background
+- Auto-scroll to bottom on new entries; pauses when user scrolls up; resumes on scroll-to-bottom
+
+**File location:** `src/design-system/composites/LogViewer/`
+
+**Pages to refactor:** AgentInstance.tsx — replace custom log rendering with ``.
+
+---
+
+## 5. StatusText — New Primitive
+
+**Problem:** Dashboard and Routes use inline `style={{ color: 'var(--error)', fontWeight: 600 }}` for status values like "BREACH", "OK", colored percentages.
+
+**Solution:** New primitive `StatusText`.
+
+```tsx
+interface StatusTextProps {
+ variant: 'success' | 'warning' | 'error' | 'running' | 'muted'
+ bold?: boolean // Default: false
+ children: ReactNode
+ className?: string
+}
+```
+
+**Styling:**
+- Inline `` element
+- Color mapped to semantic tokens: success=`var(--success)`, warning=`var(--warning)`, error=`var(--error)`, running=`var(--running)`, muted=`var(--text-muted)`
+- `bold` adds `font-weight: 600`
+- Inherits font-size from parent
+
+**File location:** `src/design-system/primitives/StatusText/`
+
+**Pages to refactor:** Dashboard.tsx, Routes.tsx — replace inline style attributes with ``.
+
+---
+
+## 6. Card Title Extension
+
+**Problem:** Routes page wraps charts in custom divs with uppercase titles. The existing `Card` component has no title support.
+
+**Solution:** Add optional `title` prop to existing `Card` primitive.
+
+```tsx
+interface CardProps {
+ children: ReactNode
+ accent?: string // Existing
+ title?: string // NEW
+ className?: string // Existing
+}
+```
+
+**When `title` is provided:**
+- Renders a header div inside the card, above children
+- Title: 11px uppercase, `var(--font-mono)`, weight 600, `var(--text-secondary)`, letter-spacing 0.5px
+- Separated from content by 1px `var(--border-subtle)` bottom border and 12px padding-bottom
+- Content area gets 16px padding-top
+
+**File location:** Modify existing `src/design-system/primitives/Card/Card.tsx`
+
+**Pages to refactor:** Routes.tsx — replace custom chart wrapper divs with ``.
+
+---
+
+## Implementation Priority
+
+1. **KpiStrip** — highest impact, 3 pages, ~320 lines eliminated
+2. **StatusText** — smallest scope, quick win, unblocks cleaner page code
+3. **Card title** — small change to existing component, unblocks Routes cleanup
+4. **SplitPane** — 3 admin tabs, clean pattern
+5. **LogViewer** — 1 page but important for real app
+6. **AgentHealth DataTable refactor** — pure page cleanup, no DS changes
+
+## Testing
+
+All new components tested with Vitest + React Testing Library, co-located test files. Page refactors verified by running existing tests + visual check that pages look identical before and after.
+
+## Barrel Exports
+
+New components added to respective barrel exports:
+- `src/design-system/primitives/index.ts` — StatusText
+- `src/design-system/composites/index.ts` — KpiStrip, SplitPane, LogViewer