Files
design-system/docs/superpowers/plans/2026-03-24-metrics-components.md
hsiegeln e664e449c3 docs: add 4 implementation plans for mock deviation cleanup
Plan 1: KpiStrip + StatusText + Card title (metrics)
Plan 2: SplitPane + EntityList (admin)
Plan 3: LogViewer + AgentHealth DataTable refactor (observability)
Plan 4: COMPONENT_GUIDE.md + Inventory updates (documentation)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 12:10:00 +01:00

21 KiB

Metrics Components Implementation Plan

For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (- [ ]) syntax for tracking.

Goal: Add StatusText primitive, Card title prop, and KpiStrip composite to eliminate ~320 lines of duplicated KPI layout code across Dashboard, Routes, and AgentHealth pages.

Architecture: StatusText is a tiny inline span primitive with semantic color variants. Card gets an optional title prop for a header row. KpiStrip is a new composite that renders a horizontal row of metric cards with labels, values, trends, subtitles, and sparklines.

Tech Stack: React, TypeScript, CSS Modules, Vitest, React Testing Library

Spec: docs/superpowers/specs/2026-03-24-mock-deviations-design.md (Sections 1, 5, 6)


File Map

Action File Task
CREATE src/design-system/primitives/StatusText/StatusText.tsx 1
CREATE src/design-system/primitives/StatusText/StatusText.module.css 1
CREATE src/design-system/primitives/StatusText/StatusText.test.tsx 1
MODIFY src/design-system/primitives/index.ts 1
MODIFY src/design-system/primitives/Card/Card.tsx 2
MODIFY src/design-system/primitives/Card/Card.module.css 2
CREATE src/design-system/primitives/Card/Card.test.tsx 2
CREATE src/design-system/composites/KpiStrip/KpiStrip.tsx 3
CREATE src/design-system/composites/KpiStrip/KpiStrip.module.css 3
CREATE src/design-system/composites/KpiStrip/KpiStrip.test.tsx 3
MODIFY src/design-system/composites/index.ts 3

Task 1: StatusText Primitive

Files:

  • CREATE src/design-system/primitives/StatusText/StatusText.tsx
  • CREATE src/design-system/primitives/StatusText/StatusText.module.css
  • CREATE src/design-system/primitives/StatusText/StatusText.test.tsx
  • MODIFY src/design-system/primitives/index.ts

Step 1.1 — Write test (RED)

  • Create src/design-system/primitives/StatusText/StatusText.test.tsx:
import { describe, it, expect } from 'vitest'
import { render, screen } from '@testing-library/react'
import { StatusText } from './StatusText'

describe('StatusText', () => {
  it('renders children text', () => {
    render(<StatusText variant="success">OK</StatusText>)
    expect(screen.getByText('OK')).toBeInTheDocument()
  })

  it('renders as a span element', () => {
    render(<StatusText variant="success">OK</StatusText>)
    expect(screen.getByText('OK').tagName).toBe('SPAN')
  })

  it('applies variant class', () => {
    render(<StatusText variant="error">BREACH</StatusText>)
    expect(screen.getByText('BREACH')).toHaveClass('error')
  })

  it('applies bold class when bold=true', () => {
    render(<StatusText variant="warning" bold>HIGH</StatusText>)
    expect(screen.getByText('HIGH')).toHaveClass('bold')
  })

  it('does not apply bold class by default', () => {
    render(<StatusText variant="muted">idle</StatusText>)
    expect(screen.getByText('idle')).not.toHaveClass('bold')
  })

  it('accepts custom className', () => {
    render(<StatusText variant="running" className="custom">active</StatusText>)
    expect(screen.getByText('active')).toHaveClass('custom')
  })

  it('renders all variant classes correctly', () => {
    const { rerender } = render(<StatusText variant="success">text</StatusText>)
    expect(screen.getByText('text')).toHaveClass('success')

    rerender(<StatusText variant="warning">text</StatusText>)
    expect(screen.getByText('text')).toHaveClass('warning')

    rerender(<StatusText variant="error">text</StatusText>)
    expect(screen.getByText('text')).toHaveClass('error')

    rerender(<StatusText variant="running">text</StatusText>)
    expect(screen.getByText('text')).toHaveClass('running')

    rerender(<StatusText variant="muted">text</StatusText>)
    expect(screen.getByText('text')).toHaveClass('muted')
  })
})
  • Run test — expect FAIL (module not found):
npx vitest run src/design-system/primitives/StatusText/StatusText.test.tsx

Step 1.2 — Implement (GREEN)

  • Create src/design-system/primitives/StatusText/StatusText.module.css:
.statusText {
  /* Inherits font-size from parent */
}

.success { color: var(--success); }
.warning { color: var(--warning); }
.error   { color: var(--error); }
.running { color: var(--running); }
.muted   { color: var(--text-muted); }

.bold { font-weight: 600; }
  • Create src/design-system/primitives/StatusText/StatusText.tsx:
import styles from './StatusText.module.css'
import type { ReactNode } from 'react'

interface StatusTextProps {
  variant: 'success' | 'warning' | 'error' | 'running' | 'muted'
  bold?: boolean
  children: ReactNode
  className?: string
}

export function StatusText({ variant, bold = false, children, className }: StatusTextProps) {
  const classes = [
    styles.statusText,
    styles[variant],
    bold ? styles.bold : '',
    className ?? '',
  ].filter(Boolean).join(' ')

  return <span className={classes}>{children}</span>
}
  • Run test — expect PASS:
npx vitest run src/design-system/primitives/StatusText/StatusText.test.tsx

Step 1.3 — Barrel export

  • Add to src/design-system/primitives/index.ts (alphabetical, after StatusDot):
export { StatusText } from './StatusText/StatusText'

Step 1.4 — Commit

git add src/design-system/primitives/StatusText/ src/design-system/primitives/index.ts
git commit -m "feat: add StatusText primitive with semantic color variants"

Task 2: Card Title Extension

Files:

  • MODIFY src/design-system/primitives/Card/Card.tsx
  • MODIFY src/design-system/primitives/Card/Card.module.css
  • CREATE src/design-system/primitives/Card/Card.test.tsx

Step 2.1 — Write test (RED)

  • Create src/design-system/primitives/Card/Card.test.tsx:
import { describe, it, expect } from 'vitest'
import { render, screen } from '@testing-library/react'
import { Card } from './Card'

describe('Card', () => {
  it('renders children', () => {
    render(<Card>Card content</Card>)
    expect(screen.getByText('Card content')).toBeInTheDocument()
  })

  it('renders title when provided', () => {
    render(<Card title="Section Title">content</Card>)
    expect(screen.getByText('Section Title')).toBeInTheDocument()
  })

  it('does not render title header when title is omitted', () => {
    const { container } = render(<Card>content</Card>)
    expect(container.querySelector('.titleHeader')).not.toBeInTheDocument()
  })

  it('wraps children in body div when title is provided', () => {
    render(<Card title="Header">body text</Card>)
    const body = screen.getByText('body text').closest('div')
    expect(body).toHaveClass('body')
  })

  it('renders with accent and title together', () => {
    const { container } = render(
      <Card accent="success" title="Status">
        details
      </Card>
    )
    expect(container.firstChild).toHaveClass('accent-success')
    expect(screen.getByText('Status')).toBeInTheDocument()
    expect(screen.getByText('details')).toBeInTheDocument()
  })

  it('accepts className prop', () => {
    const { container } = render(<Card className="custom">content</Card>)
    expect(container.firstChild).toHaveClass('custom')
  })

  it('renders children directly when no title (no wrapper div)', () => {
    const { container } = render(<Card><span data-testid="direct">hi</span></Card>)
    expect(screen.getByTestId('direct')).toBeInTheDocument()
    // Should not have a body wrapper when there is no title
    expect(container.querySelector('.body')).not.toBeInTheDocument()
  })
})
  • Run test — expect FAIL (title prop not supported yet, body class missing):
npx vitest run src/design-system/primitives/Card/Card.test.tsx

Step 2.2 — Implement (GREEN)

  • Add to src/design-system/primitives/Card/Card.module.css (append after existing rules):
.titleHeader {
  padding: 12px 16px;
  border-bottom: 1px solid var(--border-subtle);
}

.titleText {
  font-size: 11px;
  text-transform: uppercase;
  font-family: var(--font-mono);
  font-weight: 600;
  color: var(--text-secondary);
  letter-spacing: 0.5px;
  margin: 0;
}

.body {
  padding: 16px;
}
  • Replace src/design-system/primitives/Card/Card.tsx with:
import styles from './Card.module.css'
import type { ReactNode } from 'react'

interface CardProps {
  children: ReactNode
  accent?: 'amber' | 'success' | 'warning' | 'error' | 'running' | 'none'
  title?: string
  className?: string
}

export function Card({ children, accent = 'none', title, className }: CardProps) {
  const classes = [
    styles.card,
    accent !== 'none' ? styles[`accent-${accent}`] : '',
    className ?? '',
  ].filter(Boolean).join(' ')

  return (
    <div className={classes}>
      {title && (
        <div className={styles.titleHeader}>
          <h3 className={styles.titleText}>{title}</h3>
        </div>
      )}
      {title ? <div className={styles.body}>{children}</div> : children}
    </div>
  )
}
  • Run test — expect PASS:
npx vitest run src/design-system/primitives/Card/Card.test.tsx

Step 2.3 — Commit

git add src/design-system/primitives/Card/
git commit -m "feat: add optional title prop to Card primitive"

Task 3: KpiStrip Composite

Files:

  • CREATE src/design-system/composites/KpiStrip/KpiStrip.tsx
  • CREATE src/design-system/composites/KpiStrip/KpiStrip.module.css
  • CREATE src/design-system/composites/KpiStrip/KpiStrip.test.tsx
  • MODIFY src/design-system/composites/index.ts

Step 3.1 — Write test (RED)

  • Create src/design-system/composites/KpiStrip/KpiStrip.test.tsx:
import { describe, it, expect } from 'vitest'
import { render, screen } from '@testing-library/react'
import { KpiStrip } from './KpiStrip'

const sampleItems = [
  {
    label: 'Total Throughput',
    value: '12,847',
    trend: { label: '\u25B2 +8%', variant: 'success' as const },
    subtitle: '35.7 msg/s',
    sparkline: [44, 46, 45, 47, 48, 46, 47],
    borderColor: 'var(--amber)',
  },
  {
    label: 'Error Rate',
    value: '0.42%',
    trend: { label: '\u25BC -0.1%', variant: 'success' as const },
    subtitle: '54 errors / 12,847 total',
  },
  {
    label: 'Active Routes',
    value: 14,
  },
]

describe('KpiStrip', () => {
  it('renders all items', () => {
    render(<KpiStrip items={sampleItems} />)
    expect(screen.getByText('Total Throughput')).toBeInTheDocument()
    expect(screen.getByText('Error Rate')).toBeInTheDocument()
    expect(screen.getByText('Active Routes')).toBeInTheDocument()
  })

  it('renders labels and values', () => {
    render(<KpiStrip items={sampleItems} />)
    expect(screen.getByText('12,847')).toBeInTheDocument()
    expect(screen.getByText('0.42%')).toBeInTheDocument()
    expect(screen.getByText('14')).toBeInTheDocument()
  })

  it('renders trend with correct text', () => {
    render(<KpiStrip items={sampleItems} />)
    expect(screen.getByText('\u25B2 +8%')).toBeInTheDocument()
    expect(screen.getByText('\u25BC -0.1%')).toBeInTheDocument()
  })

  it('applies variant class to trend', () => {
    render(<KpiStrip items={sampleItems} />)
    const trend = screen.getByText('\u25B2 +8%')
    expect(trend).toHaveClass('trendSuccess')
  })

  it('hides trend when omitted', () => {
    render(<KpiStrip items={[{ label: 'Routes', value: 14 }]} />)
    // Should only have label and value, no trend element
    const card = screen.getByText('Routes').closest('[class*="kpiCard"]')
    expect(card?.querySelector('[class*="trend"]')).toBeNull()
  })

  it('renders subtitle', () => {
    render(<KpiStrip items={sampleItems} />)
    expect(screen.getByText('35.7 msg/s')).toBeInTheDocument()
    expect(screen.getByText('54 errors / 12,847 total')).toBeInTheDocument()
  })

  it('renders sparkline when data provided', () => {
    const { container } = render(<KpiStrip items={sampleItems} />)
    // Sparkline renders an SVG with aria-hidden
    const svgs = container.querySelectorAll('svg[aria-hidden="true"]')
    expect(svgs.length).toBe(1) // Only first item has sparkline
  })

  it('accepts className prop', () => {
    const { container } = render(<KpiStrip items={sampleItems} className="custom" />)
    expect(container.firstChild).toHaveClass('custom')
  })

  it('handles empty items array', () => {
    const { container } = render(<KpiStrip items={[]} />)
    expect(container.firstChild).toBeInTheDocument()
    // No cards rendered
    expect(container.querySelectorAll('[class*="kpiCard"]').length).toBe(0)
  })

  it('uses default border color when borderColor is omitted', () => {
    const { container } = render(
      <KpiStrip items={[{ label: 'Test', value: 100 }]} />
    )
    const card = container.querySelector('[class*="kpiCard"]')
    expect(card).toBeInTheDocument()
    // The default borderColor is applied via inline style
    expect(card).toHaveStyle({ '--kpi-border-color': 'var(--amber)' })
  })

  it('applies custom borderColor', () => {
    const { container } = render(
      <KpiStrip items={[{ label: 'Errors', value: 5, borderColor: 'var(--error)' }]} />
    )
    const card = container.querySelector('[class*="kpiCard"]')
    expect(card).toHaveStyle({ '--kpi-border-color': 'var(--error)' })
  })

  it('renders trend with muted variant by default', () => {
    render(
      <KpiStrip items={[{ label: 'Test', value: 1, trend: { label: '~ stable' } }]} />
    )
    const trend = screen.getByText('~ stable')
    expect(trend).toHaveClass('trendMuted')
  })
})
  • Run test — expect FAIL (module not found):
npx vitest run src/design-system/composites/KpiStrip/KpiStrip.test.tsx

Step 3.2 — Implement (GREEN)

  • Create src/design-system/composites/KpiStrip/KpiStrip.module.css:
/* KpiStrip — horizontal row of metric cards */
.kpiStrip {
  display: grid;
  gap: 12px;
  margin-bottom: 20px;
}

/* ── Individual card ─────────────────────────────────────────────── */
.kpiCard {
  background: var(--bg-surface);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-lg);
  padding: 16px 18px 12px;
  box-shadow: var(--shadow-card);
  position: relative;
  overflow: hidden;
  transition: box-shadow 0.15s;
}

.kpiCard:hover {
  box-shadow: var(--shadow-md);
}

/* Top gradient border — color driven by CSS custom property */
.kpiCard::before {
  content: '';
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  height: 3px;
  background: linear-gradient(90deg, var(--kpi-border-color), transparent);
}

/* ── Label ───────────────────────────────────────────────────────── */
.label {
  font-size: 10px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.6px;
  color: var(--text-muted);
  margin-bottom: 6px;
}

/* ── Value row ───────────────────────────────────────────────────── */
.valueRow {
  display: flex;
  align-items: baseline;
  gap: 6px;
  margin-bottom: 4px;
}

.value {
  font-family: var(--font-mono);
  font-size: 26px;
  font-weight: 600;
  line-height: 1.2;
  color: var(--text-primary);
}

/* ── Trend ────────────────────────────────────────────────────────── */
.trend {
  font-family: var(--font-mono);
  font-size: 11px;
  display: inline-flex;
  align-items: center;
  gap: 2px;
  margin-left: auto;
}

.trendSuccess { color: var(--success); }
.trendWarning { color: var(--warning); }
.trendError   { color: var(--error); }
.trendMuted   { color: var(--text-muted); }

/* ── Subtitle ─────────────────────────────────────────────────────── */
.subtitle {
  font-size: 11px;
  color: var(--text-muted);
  margin-top: 2px;
}

/* ── Sparkline ────────────────────────────────────────────────────── */
.sparkline {
  margin-top: 8px;
  height: 32px;
}
  • Create src/design-system/composites/KpiStrip/KpiStrip.tsx:
import styles from './KpiStrip.module.css'
import { Sparkline } from '../../primitives/Sparkline/Sparkline'
import type { CSSProperties, ReactNode } from 'react'

export interface KpiItem {
  label: string
  value: string | number
  trend?: { label: string; variant?: 'success' | 'warning' | 'error' | 'muted' }
  subtitle?: string
  sparkline?: number[]
  borderColor?: string
}

export interface KpiStripProps {
  items: KpiItem[]
  className?: string
}

const trendClassMap: Record<string, string> = {
  success: styles.trendSuccess,
  warning: styles.trendWarning,
  error: styles.trendError,
  muted: styles.trendMuted,
}

export function KpiStrip({ items, className }: KpiStripProps) {
  const stripClasses = [styles.kpiStrip, className ?? ''].filter(Boolean).join(' ')
  const gridStyle: CSSProperties = {
    gridTemplateColumns: items.length > 0 ? `repeat(${items.length}, 1fr)` : undefined,
  }

  return (
    <div className={stripClasses} style={gridStyle}>
      {items.map((item) => {
        const borderColor = item.borderColor ?? 'var(--amber)'
        const cardStyle: CSSProperties & Record<string, string> = {
          '--kpi-border-color': borderColor,
        }
        const trendVariant = item.trend?.variant ?? 'muted'
        const trendClass = trendClassMap[trendVariant] ?? styles.trendMuted

        return (
          <div key={item.label} className={styles.kpiCard} style={cardStyle}>
            <div className={styles.label}>{item.label}</div>
            <div className={styles.valueRow}>
              <span className={styles.value}>{item.value}</span>
              {item.trend && (
                <span className={`${styles.trend} ${trendClass}`}>
                  {item.trend.label}
                </span>
              )}
            </div>
            {item.subtitle && (
              <div className={styles.subtitle}>{item.subtitle}</div>
            )}
            {item.sparkline && item.sparkline.length >= 2 && (
              <div className={styles.sparkline}>
                <Sparkline
                  data={item.sparkline}
                  color={borderColor}
                  width={200}
                  height={32}
                />
              </div>
            )}
          </div>
        )
      })}
    </div>
  )
}
  • Run test — expect PASS:
npx vitest run src/design-system/composites/KpiStrip/KpiStrip.test.tsx

Step 3.3 — Barrel export

  • Add to src/design-system/composites/index.ts (alphabetical, after GroupCard):
export { KpiStrip } from './KpiStrip/KpiStrip'
export type { KpiItem, KpiStripProps } from './KpiStrip/KpiStrip'

Step 3.4 — Commit

git add src/design-system/composites/KpiStrip/ src/design-system/composites/index.ts
git commit -m "feat: add KpiStrip composite for reusable metric card rows"

Task 4: Barrel Exports Verification & Full Test Run

Files:

  • VERIFY src/design-system/primitives/index.ts (modified in Task 1)
  • VERIFY src/design-system/composites/index.ts (modified in Task 3)

Step 4.1 — Verify barrel exports

  • Confirm src/design-system/primitives/index.ts contains:
export { StatusText } from './StatusText/StatusText'
  • Confirm src/design-system/composites/index.ts contains:
export { KpiStrip } from './KpiStrip/KpiStrip'
export type { KpiItem, KpiStripProps } from './KpiStrip/KpiStrip'

Step 4.2 — Run full test suite

  • Run all tests to confirm nothing is broken:
npx vitest run
  • Verify zero failures. If any test fails, fix and re-run before proceeding.

Step 4.3 — Final commit (if barrel-only changes remain)

If the barrel export changes were not already committed in their respective tasks:

git add src/design-system/primitives/index.ts src/design-system/composites/index.ts
git commit -m "chore: add StatusText and KpiStrip to barrel exports"

Summary of Expected Barrel Export Additions

src/design-system/primitives/index.ts — insert after StatusDot line:

export { StatusText } from './StatusText/StatusText'

src/design-system/composites/index.ts — insert after GroupCard line:

export { KpiStrip } from './KpiStrip/KpiStrip'
export type { KpiItem, KpiStripProps } from './KpiStrip/KpiStrip'

Test Commands Quick Reference

Scope Command
StatusText only npx vitest run src/design-system/primitives/StatusText/StatusText.test.tsx
Card only npx vitest run src/design-system/primitives/Card/Card.test.tsx
KpiStrip only npx vitest run src/design-system/composites/KpiStrip/KpiStrip.test.tsx
All tests npx vitest run