# 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 ```