diff --git a/docs/superpowers/plans/2026-04-12-recharts-migration.md b/docs/superpowers/plans/2026-04-12-recharts-migration.md new file mode 100644 index 0000000..f9caec6 --- /dev/null +++ b/docs/superpowers/plans/2026-04-12-recharts-migration.md @@ -0,0 +1,845 @@ +# Recharts Migration 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:** Replace the design system's hand-rolled SVG chart components with a single `ThemedChart` wrapper around Recharts, using Recharts-native data format. + +**Architecture:** Add Recharts as a DS dependency. Create `ThemedChart` component that renders `ResponsiveContainer` + `ComposedChart` with pre-themed grid/axes/tooltip. Consumers compose Recharts elements (``, ``, ``, ``) as children. Delete old `LineChart/`, `AreaChart/`, `BarChart/`, `_chart-utils.ts`. Migrate the server UI's `AgentInstance.tsx` to the new API. + +**Tech Stack:** React 19, Recharts, CSS Modules, Vitest + +--- + +## File Structure + +**Design System (`C:\Users\Hendrik\Documents\projects\design-system`):** + +| Action | Path | Purpose | +|--------|------|---------| +| Create | `src/design-system/composites/ThemedChart/ThemedChart.tsx` | Wrapper component: ResponsiveContainer + ComposedChart + themed axes/grid/tooltip | +| Create | `src/design-system/composites/ThemedChart/ChartTooltip.tsx` | Custom tooltip with timestamp header + series values | +| Create | `src/design-system/composites/ThemedChart/ChartTooltip.module.css` | Tooltip styles using DS tokens | +| Create | `src/design-system/composites/ThemedChart/ThemedChart.test.tsx` | Render tests for ThemedChart | +| Modify | `src/design-system/utils/rechartsTheme.ts` | Move `CHART_COLORS` definition here (was in `_chart-utils.ts`) | +| Modify | `src/design-system/composites/index.ts` | Remove old chart exports, add ThemedChart + Recharts re-exports | +| Modify | `COMPONENT_GUIDE.md` | Update charting strategy section | +| Modify | `package.json` | Add `recharts` dependency, bump version | +| Delete | `src/design-system/composites/LineChart/` | Old hand-rolled SVG line chart | +| Delete | `src/design-system/composites/AreaChart/` | Old hand-rolled SVG area chart | +| Delete | `src/design-system/composites/BarChart/` | Old hand-rolled SVG bar chart | +| Delete | `src/design-system/composites/_chart-utils.ts` | Old chart utilities | + +**Server UI (`C:\Users\Hendrik\Documents\projects\cameleer3-server`):** + +| Action | Path | Purpose | +|--------|------|---------| +| Modify | `ui/src/pages/AgentInstance/AgentInstance.tsx` | Migrate 6 charts to ThemedChart + Recharts children | +| Modify | `ui/package.json` | Update `@cameleer/design-system` to new version | + +--- + +### Task 1: Add Recharts Dependency and Move CHART_COLORS + +**Files:** +- Modify: `package.json` +- Modify: `src/design-system/utils/rechartsTheme.ts` + +- [ ] **Step 1: Install recharts** + +```bash +cd C:\Users\Hendrik\Documents\projects\design-system +npm install recharts +``` + +- [ ] **Step 2: Move CHART_COLORS into rechartsTheme.ts** + +Replace the entire file `src/design-system/utils/rechartsTheme.ts` with: + +```tsx +export const CHART_COLORS = [ + 'var(--chart-1)', + 'var(--chart-2)', + 'var(--chart-3)', + 'var(--chart-4)', + 'var(--chart-5)', + 'var(--chart-6)', + 'var(--chart-7)', + 'var(--chart-8)', +] + +/** + * Pre-configured Recharts prop objects that match the design system's + * chart styling. Used internally by ThemedChart and available for + * consumers composing Recharts directly. + */ +export const rechartsTheme = { + colors: CHART_COLORS, + + cartesianGrid: { + stroke: 'var(--border-subtle)', + strokeDasharray: '3 3', + vertical: false, + }, + + xAxis: { + tick: { fontSize: 9, fontFamily: 'var(--font-mono)', fill: 'var(--text-faint)' }, + axisLine: { stroke: 'var(--border-subtle)' }, + tickLine: false as const, + }, + + yAxis: { + tick: { fontSize: 9, fontFamily: 'var(--font-mono)', fill: 'var(--text-faint)' }, + axisLine: false as const, + tickLine: false as const, + }, + + tooltip: { + contentStyle: { + background: 'var(--bg-surface)', + border: '1px solid var(--border)', + borderRadius: 'var(--radius-sm)', + boxShadow: 'var(--shadow-md)', + fontSize: 11, + padding: '6px 10px', + }, + labelStyle: { + color: 'var(--text-muted)', + fontSize: 11, + marginBottom: 4, + }, + itemStyle: { + color: 'var(--text-primary)', + fontFamily: 'var(--font-mono)', + fontSize: 11, + padding: 0, + }, + cursor: { stroke: 'var(--text-faint)' }, + }, + + legend: { + wrapperStyle: { + fontSize: 11, + color: 'var(--text-secondary)', + }, + }, +} as const +``` + +- [ ] **Step 3: Verify build compiles** + +```bash +npm run build:lib +``` + +Expected: Build succeeds. The old chart components still import `CHART_COLORS` from `_chart-utils.ts` which still exists — they'll be deleted in Task 4. + +- [ ] **Step 4: Commit** + +```bash +git add package.json package-lock.json src/design-system/utils/rechartsTheme.ts +git commit -m "chore: add recharts dependency, move CHART_COLORS to rechartsTheme" +``` + +--- + +### Task 2: Create ChartTooltip Component + +**Files:** +- Create: `src/design-system/composites/ThemedChart/ChartTooltip.tsx` +- Create: `src/design-system/composites/ThemedChart/ChartTooltip.module.css` + +- [ ] **Step 1: Create tooltip CSS** + +Create `src/design-system/composites/ThemedChart/ChartTooltip.module.css`: + +```css +.tooltip { + background: var(--bg-surface); + border: 1px solid var(--border); + border-radius: var(--radius-sm); + box-shadow: var(--shadow-md); + padding: 6px 10px; + font-size: 12px; +} + +.time { + font-family: var(--font-mono); + font-size: 10px; + color: var(--text-muted); + margin-bottom: 4px; + padding-bottom: 3px; + border-bottom: 1px solid var(--border-subtle); +} + +.row { + display: flex; + align-items: center; + gap: 5px; + margin-bottom: 2px; +} + +.row:last-child { + margin-bottom: 0; +} + +.dot { + width: 6px; + height: 6px; + border-radius: 50%; + flex-shrink: 0; +} + +.label { + color: var(--text-muted); + font-size: 11px; +} + +.value { + font-family: var(--font-mono); + font-weight: 600; + font-size: 11px; + color: var(--text-primary); +} +``` + +- [ ] **Step 2: Create ChartTooltip component** + +Create `src/design-system/composites/ThemedChart/ChartTooltip.tsx`: + +```tsx +import type { TooltipProps } from 'recharts' +import styles from './ChartTooltip.module.css' + +function formatValue(val: number): string { + if (val >= 1_000_000) return `${(val / 1_000_000).toFixed(1)}M` + if (val >= 1000) return `${(val / 1000).toFixed(1)}k` + if (Number.isInteger(val)) return String(val) + return val.toFixed(1) +} + +function formatTimestamp(val: unknown): string | null { + if (val == null) return null + const str = String(val) + const ms = typeof val === 'number' && val > 1e12 ? val + : typeof val === 'number' && val > 1e9 ? val * 1000 + : Date.parse(str) + if (isNaN(ms)) return str + return new Date(ms).toLocaleString([], { + month: 'short', day: 'numeric', + hour: '2-digit', minute: '2-digit', second: '2-digit', + }) +} + +export function ChartTooltip({ active, payload, label }: TooltipProps) { + if (!active || !payload?.length) return null + + const timeLabel = formatTimestamp(label) + + return ( +
+ {timeLabel &&
{timeLabel}
} + {payload.map((entry) => ( +
+ + {entry.name}: + {formatValue(entry.value as number)} +
+ ))} +
+ ) +} +``` + +- [ ] **Step 3: Commit** + +```bash +git add src/design-system/composites/ThemedChart/ +git commit -m "feat: add ChartTooltip component for ThemedChart" +``` + +--- + +### Task 3: Create ThemedChart Component + +**Files:** +- Create: `src/design-system/composites/ThemedChart/ThemedChart.tsx` +- Create: `src/design-system/composites/ThemedChart/ThemedChart.test.tsx` + +- [ ] **Step 1: Create ThemedChart component** + +Create `src/design-system/composites/ThemedChart/ThemedChart.tsx`: + +```tsx +import { + ResponsiveContainer, + ComposedChart, + CartesianGrid, + XAxis, + YAxis, + Tooltip, +} from 'recharts' +import { rechartsTheme } from '../../utils/rechartsTheme' +import { ChartTooltip } from './ChartTooltip' + +interface ThemedChartProps { + data: Record[] + height?: number + xDataKey?: string + xType?: 'number' | 'category' + xTickFormatter?: (value: any) => string + yTickFormatter?: (value: any) => string + yLabel?: string + children: React.ReactNode + className?: string +} + +export function ThemedChart({ + data, + height = 200, + xDataKey = 'time', + xType = 'category', + xTickFormatter, + yTickFormatter, + yLabel, + children, + className, +}: ThemedChartProps) { + if (!data.length) { + return null + } + + return ( +
+ + + + + + } cursor={rechartsTheme.tooltip.cursor} /> + {children} + + +
+ ) +} +``` + +- [ ] **Step 2: Write test** + +Create `src/design-system/composites/ThemedChart/ThemedChart.test.tsx`: + +```tsx +import { describe, it, expect } from 'vitest' +import { render, screen } from '@testing-library/react' +import { ThemedChart } from './ThemedChart' +import { Line } from 'recharts' + +// Recharts uses ResizeObserver internally +class ResizeObserverMock { + observe() {} + unobserve() {} + disconnect() {} +} +globalThis.ResizeObserver = ResizeObserverMock as any + +describe('ThemedChart', () => { + it('renders nothing when data is empty', () => { + const { container } = render( + + + , + ) + expect(container.innerHTML).toBe('') + }) + + it('renders a chart container when data is provided', () => { + const data = [ + { time: '10:00', value: 10 }, + { time: '10:01', value: 20 }, + ] + const { container } = render( + + + , + ) + expect(container.querySelector('.recharts-responsive-container')).toBeTruthy() + }) + + it('applies custom className', () => { + const data = [{ time: '10:00', value: 5 }] + const { container } = render( + + + , + ) + expect(container.querySelector('.my-chart')).toBeTruthy() + }) +}) +``` + +- [ ] **Step 3: Run tests** + +```bash +npx vitest run src/design-system/composites/ThemedChart/ThemedChart.test.tsx +``` + +Expected: 3 tests pass. + +- [ ] **Step 4: Verify lib build** + +```bash +npm run build:lib +``` + +Expected: Build succeeds. + +- [ ] **Step 5: Commit** + +```bash +git add src/design-system/composites/ThemedChart/ +git commit -m "feat: add ThemedChart wrapper component" +``` + +--- + +### Task 4: Update Barrel Exports and Delete Old Charts + +**Files:** +- Modify: `src/design-system/composites/index.ts` +- Delete: `src/design-system/composites/LineChart/` +- Delete: `src/design-system/composites/AreaChart/` +- Delete: `src/design-system/composites/BarChart/` +- Delete: `src/design-system/composites/_chart-utils.ts` + +- [ ] **Step 1: Update composites/index.ts** + +Remove these lines: +``` +export { AreaChart } from './AreaChart/AreaChart' +export { BarChart } from './BarChart/BarChart' +export { LineChart } from './LineChart/LineChart' +export { CHART_COLORS } from './_chart-utils' +export type { ChartSeries, DataPoint } from './_chart-utils' +``` + +Add in their place: +```tsx +// Charts — ThemedChart wrapper + Recharts re-exports +export { ThemedChart } from './ThemedChart/ThemedChart' +export { CHART_COLORS, rechartsTheme } from '../utils/rechartsTheme' +export { + Line, Area, Bar, + ReferenceLine, ReferenceArea, + Legend, Brush, +} from 'recharts' +``` + +- [ ] **Step 2: Remove the rechartsTheme re-export from main index.ts** + +In `src/design-system/index.ts`, remove this line (it's now re-exported via composites): +``` +export * from './utils/rechartsTheme' +``` + +Replace with a targeted export that avoids double-exporting `CHART_COLORS`: +```tsx +export { rechartsTheme } from './utils/rechartsTheme' +``` + +Wait — actually `composites/index.ts` already re-exports both `CHART_COLORS` and `rechartsTheme`. And `index.ts` does `export * from './composites'`. So the main `index.ts` line `export * from './utils/rechartsTheme'` would cause a duplicate export of both symbols. Remove it entirely: + +Delete this line from `src/design-system/index.ts`: +``` +export * from './utils/rechartsTheme' +``` + +- [ ] **Step 3: Delete old chart directories and utilities** + +```bash +rm -rf src/design-system/composites/LineChart +rm -rf src/design-system/composites/AreaChart +rm -rf src/design-system/composites/BarChart +rm src/design-system/composites/_chart-utils.ts +``` + +- [ ] **Step 4: Verify lib build** + +```bash +npm run build:lib +``` + +Expected: Build succeeds. The old components are gone, ThemedChart and Recharts re-exports are the new public API. + +- [ ] **Step 5: Run all tests** + +```bash +npx vitest run +``` + +Expected: All tests pass. No test files existed for the deleted components. + +- [ ] **Step 6: Commit** + +```bash +git add -A +git commit -m "feat!: replace custom chart components with ThemedChart + Recharts + +BREAKING: LineChart, AreaChart, BarChart, ChartSeries, DataPoint removed. +Use ThemedChart with Recharts children (Line, Area, Bar, etc.) instead." +``` + +--- + +### Task 5: Update COMPONENT_GUIDE.md + +**Files:** +- Modify: `COMPONENT_GUIDE.md` + +- [ ] **Step 1: Update the charting strategy section** + +Find the section starting with `## Charting Strategy` (around line 183) and replace through line 228 with: + +```markdown +## Charting Strategy + +The design system provides a **ThemedChart** wrapper component that applies consistent styling to Recharts charts. Recharts is bundled as a dependency — consumers do not need to install it separately. + +### Usage + +```tsx +import { ThemedChart, Line, Area, ReferenceLine, CHART_COLORS } from '@cameleer/design-system' + +const data = metrics.map(m => ({ time: m.timestamp, cpu: m.value * 100 })) + + + + + +``` + +### ThemedChart Props + +| Prop | Type | Default | Description | +|------|------|---------|-------------| +| `data` | `Record[]` | required | Flat array of data objects | +| `height` | `number` | `200` | Chart height in pixels | +| `xDataKey` | `string` | `"time"` | Key for x-axis values | +| `xType` | `'number' \| 'category'` | `"category"` | X-axis scale type | +| `xTickFormatter` | `(value: any) => string` | — | Custom x-axis label formatter | +| `yTickFormatter` | `(value: any) => string` | — | Custom y-axis label formatter | +| `yLabel` | `string` | — | Y-axis label text | +| `children` | `ReactNode` | required | Recharts elements (Line, Area, Bar, etc.) | +| `className` | `string` | — | Container CSS class | + +### Available Recharts Re-exports + +`Line`, `Area`, `Bar`, `ReferenceLine`, `ReferenceArea`, `Legend`, `Brush` + +For chart types not covered (treemap, radar, pie, sankey), import from `recharts` directly and use `rechartsTheme` for consistent styling. + +### Theme Utilities + +| Export | Purpose | +|--------|---------| +| `CHART_COLORS` | Array of `var(--chart-1)` through `var(--chart-8)` | +| `rechartsTheme` | Pre-configured prop objects for Recharts sub-components | +``` + +- [ ] **Step 2: Update the component index table** + +Find the rows for `AreaChart`, `BarChart`, `LineChart` in the component index table and replace all three with: + +``` +| ThemedChart | composite | Recharts wrapper with themed axes, grid, and tooltip | +``` + +- [ ] **Step 3: Update the decision tree** + +Find lines 57-60 (the chart decision tree entries): +``` +- Time series → **LineChart**, **AreaChart** +- Categorical comparison → **BarChart** +``` + +Replace with: +``` +- Time series → **ThemedChart** with `` or `` +- Categorical comparison → **ThemedChart** with `` +``` + +- [ ] **Step 4: Commit** + +```bash +git add COMPONENT_GUIDE.md +git commit -m "docs: update COMPONENT_GUIDE for ThemedChart migration" +``` + +--- + +### Task 6: Publish Design System + +**Files:** +- Modify: `package.json` (version bump) + +- [ ] **Step 1: Bump version** + +In `package.json`, change `"version"` to `"0.1.47"`. + +- [ ] **Step 2: Build and verify** + +```bash +npm run build:lib +``` + +- [ ] **Step 3: Commit and tag** + +```bash +git add package.json +git commit -m "chore: bump version to 0.1.47" +git tag v0.1.47 +git push && git push --tags +``` + +- [ ] **Step 4: Wait for CI to publish** + +Wait for the Gitea CI pipeline to build and publish `@cameleer/design-system@0.1.47` to the npm registry. Verify with: + +```bash +npm view @cameleer/design-system@0.1.47 version +``` + +--- + +### Task 7: Migrate Server UI AgentInstance Charts + +**Files:** +- Modify: `C:\Users\Hendrik\Documents\projects\cameleer3-server\ui\src\pages\AgentInstance\AgentInstance.tsx` +- Modify: `C:\Users\Hendrik\Documents\projects\cameleer3-server\ui\package.json` + +- [ ] **Step 1: Update design system dependency** + +```bash +cd C:\Users\Hendrik\Documents\projects\cameleer3-server\ui +npm install @cameleer/design-system@0.1.47 +``` + +- [ ] **Step 2: Update imports in AgentInstance.tsx** + +Replace the chart-related imports: + +Old: +```tsx +import { + StatCard, StatusDot, Badge, LineChart, AreaChart, BarChart, + EventFeed, Spinner, EmptyState, SectionHeader, MonoText, + LogViewer, ButtonGroup, useGlobalFilters, +} from '@cameleer/design-system' +``` + +New: +```tsx +import { + StatCard, StatusDot, Badge, + ThemedChart, Line, Area, ReferenceLine, CHART_COLORS, + EventFeed, Spinner, EmptyState, SectionHeader, MonoText, + LogViewer, ButtonGroup, useGlobalFilters, +} from '@cameleer/design-system' +``` + +- [ ] **Step 3: Replace data prep — JVM metrics** + +Replace the 4 JVM series useMemo blocks (cpuSeries, heapSeries, threadSeries, gcSeries) with flat data builders: + +```tsx + const formatTime = (t: string) => + new Date(t).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }); + + // JVM chart data — merge all metrics into flat objects by time bucket + const cpuData = useMemo(() => { + const pts = jvmMetrics?.metrics?.['process.cpu.usage.value']; + if (!pts?.length) return []; + return pts.map((p: any) => ({ time: p.time, cpu: p.value * 100 })); + }, [jvmMetrics]); + + const heapData = useMemo(() => { + const pts = jvmMetrics?.metrics?.['jvm.memory.used.value']; + if (!pts?.length) return []; + return pts.map((p: any) => ({ time: p.time, heap: p.value / (1024 * 1024) })); + }, [jvmMetrics]); + + const threadData = useMemo(() => { + const pts = jvmMetrics?.metrics?.['jvm.threads.live.value']; + if (!pts?.length) return []; + return pts.map((p: any) => ({ time: p.time, threads: p.value })); + }, [jvmMetrics]); + + const gcData = useMemo(() => { + const pts = jvmMetrics?.metrics?.['jvm.gc.pause.total_time']; + if (!pts?.length) return []; + return pts.map((p: any) => ({ time: p.time, gc: p.value })); + }, [jvmMetrics]); +``` + +- [ ] **Step 4: Replace data prep — throughput and error** + +Replace the throughputSeries and errorSeries useMemo blocks: + +```tsx + const throughputData = useMemo(() => { + if (!chartData.length) return []; + return chartData.map((d: any) => ({ time: d.date.toISOString(), throughput: d.throughput })); + }, [chartData]); + + const errorData = useMemo(() => { + if (!chartData.length) return []; + return chartData.map((d: any) => ({ time: d.date.toISOString(), errorPct: d.errorPct })); + }, [chartData]); +``` + +- [ ] **Step 5: Replace chart JSX — CPU Usage** + +Replace the CPU chart card content: + +```tsx + {cpuData.length ? ( + + + + + ) : ( + + )} +``` + +- [ ] **Step 6: Replace chart JSX — Memory (Heap)** + +Replace the heap chart card content: + +```tsx + {heapData.length ? ( + + + {heapMax != null && ( + + )} + + ) : ( + + )} +``` + +- [ ] **Step 7: Replace chart JSX — Throughput** + +```tsx + {throughputData.length ? ( + + + + ) : ( + + )} +``` + +- [ ] **Step 8: Replace chart JSX — Error Rate** + +```tsx + {errorData.length ? ( + + + + ) : ( + + )} +``` + +- [ ] **Step 9: Replace chart JSX — Thread Count** + +```tsx + {threadData.length ? ( + + + + ) : ( + + )} +``` + +- [ ] **Step 10: Replace chart JSX — GC Pauses** + +```tsx + {gcData.length ? ( + + + + ) : ( + + )} +``` + +- [ ] **Step 11: Update the Thread Count meta display** + +The thread count meta currently reads from `threadSeries`. Update to read from `threadData`: + +Old: +```tsx +{threadSeries + ? `${threadSeries[0].data[threadSeries[0].data.length - 1]?.y.toFixed(0)} active` + : ''} +``` + +New: +```tsx +{threadData.length + ? `${threadData[threadData.length - 1].threads.toFixed(0)} active` + : ''} +``` + +- [ ] **Step 12: Build server UI** + +```bash +cd C:\Users\Hendrik\Documents\projects\cameleer3-server\ui +npm run build +``` + +Expected: Build succeeds. + +- [ ] **Step 13: Commit and push** + +```bash +cd C:\Users\Hendrik\Documents\projects\cameleer3-server +git add ui/ +git commit -m "feat: migrate agent charts to ThemedChart + Recharts + +Replace custom LineChart/AreaChart/BarChart usage with ThemedChart +wrapper. Data format changed from ChartSeries[] to Recharts-native +flat objects. Uses DS v0.1.47." +git push +```