docs: add Recharts migration implementation plan
All checks were successful
Build & Publish / publish (push) Successful in 1m2s
All checks were successful
Build & Publish / publish (push) Successful in 1m2s
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
845
docs/superpowers/plans/2026-04-12-recharts-migration.md
Normal file
845
docs/superpowers/plans/2026-04-12-recharts-migration.md
Normal file
@@ -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 (`<Line>`, `<Area>`, `<Bar>`, `<ReferenceLine>`) 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<number, string>) {
|
||||
if (!active || !payload?.length) return null
|
||||
|
||||
const timeLabel = formatTimestamp(label)
|
||||
|
||||
return (
|
||||
<div className={styles.tooltip}>
|
||||
{timeLabel && <div className={styles.time}>{timeLabel}</div>}
|
||||
{payload.map((entry) => (
|
||||
<div key={entry.dataKey as string} className={styles.row}>
|
||||
<span className={styles.dot} style={{ background: entry.color }} />
|
||||
<span className={styles.label}>{entry.name}:</span>
|
||||
<span className={styles.value}>{formatValue(entry.value as number)}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **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<string, any>[]
|
||||
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 (
|
||||
<div className={className} style={{ width: '100%', height }}>
|
||||
<ResponsiveContainer width="100%" height="100%">
|
||||
<ComposedChart data={data} margin={{ top: 4, right: 8, bottom: 0, left: 0 }}>
|
||||
<CartesianGrid {...rechartsTheme.cartesianGrid} />
|
||||
<XAxis
|
||||
dataKey={xDataKey}
|
||||
type={xType}
|
||||
{...rechartsTheme.xAxis}
|
||||
tickFormatter={xTickFormatter}
|
||||
/>
|
||||
<YAxis
|
||||
{...rechartsTheme.yAxis}
|
||||
tickFormatter={yTickFormatter}
|
||||
label={yLabel ? {
|
||||
value: yLabel,
|
||||
angle: -90,
|
||||
position: 'insideLeft',
|
||||
style: { fontSize: 11, fill: 'var(--text-muted)' },
|
||||
} : undefined}
|
||||
/>
|
||||
<Tooltip content={<ChartTooltip />} cursor={rechartsTheme.tooltip.cursor} />
|
||||
{children}
|
||||
</ComposedChart>
|
||||
</ResponsiveContainer>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **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(
|
||||
<ThemedChart data={[]}>
|
||||
<Line dataKey="value" />
|
||||
</ThemedChart>,
|
||||
)
|
||||
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(
|
||||
<ThemedChart data={data} height={160}>
|
||||
<Line dataKey="value" />
|
||||
</ThemedChart>,
|
||||
)
|
||||
expect(container.querySelector('.recharts-responsive-container')).toBeTruthy()
|
||||
})
|
||||
|
||||
it('applies custom className', () => {
|
||||
const data = [{ time: '10:00', value: 5 }]
|
||||
const { container } = render(
|
||||
<ThemedChart data={data} className="my-chart">
|
||||
<Line dataKey="value" />
|
||||
</ThemedChart>,
|
||||
)
|
||||
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 data={data} height={160} xDataKey="time" yLabel="%">
|
||||
<Area dataKey="cpu" stroke={CHART_COLORS[0]} fill={CHART_COLORS[0]} fillOpacity={0.1} />
|
||||
<ReferenceLine y={85} stroke="var(--error)" strokeDasharray="5 3" label="Alert" />
|
||||
</ThemedChart>
|
||||
```
|
||||
|
||||
### ThemedChart Props
|
||||
|
||||
| Prop | Type | Default | Description |
|
||||
|------|------|---------|-------------|
|
||||
| `data` | `Record<string, any>[]` | 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 `<Line>` or `<Area>`
|
||||
- Categorical comparison → **ThemedChart** with `<Bar>`
|
||||
```
|
||||
|
||||
- [ ] **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 ? (
|
||||
<ThemedChart data={cpuData} height={160} xDataKey="time"
|
||||
xTickFormatter={formatTime} yLabel="%">
|
||||
<Area dataKey="cpu" name="CPU %" stroke={CHART_COLORS[0]}
|
||||
fill={CHART_COLORS[0]} fillOpacity={0.1} strokeWidth={2} dot={false} />
|
||||
<ReferenceLine y={85} stroke="var(--error)" strokeDasharray="5 3"
|
||||
label={{ value: 'Alert', position: 'right', fill: 'var(--error)', fontSize: 9 }} />
|
||||
</ThemedChart>
|
||||
) : (
|
||||
<EmptyState title="No data" description="No CPU metrics available" />
|
||||
)}
|
||||
```
|
||||
|
||||
- [ ] **Step 6: Replace chart JSX — Memory (Heap)**
|
||||
|
||||
Replace the heap chart card content:
|
||||
|
||||
```tsx
|
||||
{heapData.length ? (
|
||||
<ThemedChart data={heapData} height={160} xDataKey="time"
|
||||
xTickFormatter={formatTime} yLabel="MB">
|
||||
<Area dataKey="heap" name="Heap MB" stroke={CHART_COLORS[0]}
|
||||
fill={CHART_COLORS[0]} fillOpacity={0.1} strokeWidth={2} dot={false} />
|
||||
{heapMax != null && (
|
||||
<ReferenceLine y={heapMax / (1024 * 1024)} stroke="var(--error)" strokeDasharray="5 3"
|
||||
label={{ value: 'Max Heap', position: 'right', fill: 'var(--error)', fontSize: 9 }} />
|
||||
)}
|
||||
</ThemedChart>
|
||||
) : (
|
||||
<EmptyState title="No data" description="No heap metrics available" />
|
||||
)}
|
||||
```
|
||||
|
||||
- [ ] **Step 7: Replace chart JSX — Throughput**
|
||||
|
||||
```tsx
|
||||
{throughputData.length ? (
|
||||
<ThemedChart data={throughputData} height={160} xDataKey="time"
|
||||
xTickFormatter={formatTime} yLabel="msg/s">
|
||||
<Line dataKey="throughput" name="msg/s" stroke={CHART_COLORS[0]}
|
||||
strokeWidth={2} dot={false} />
|
||||
</ThemedChart>
|
||||
) : (
|
||||
<EmptyState title="No data" description="No throughput data in range" />
|
||||
)}
|
||||
```
|
||||
|
||||
- [ ] **Step 8: Replace chart JSX — Error Rate**
|
||||
|
||||
```tsx
|
||||
{errorData.length ? (
|
||||
<ThemedChart data={errorData} height={160} xDataKey="time"
|
||||
xTickFormatter={formatTime} yLabel="%">
|
||||
<Line dataKey="errorPct" name="Error %" stroke={CHART_COLORS[0]}
|
||||
strokeWidth={2} dot={false} />
|
||||
</ThemedChart>
|
||||
) : (
|
||||
<EmptyState title="No data" description="No error data in range" />
|
||||
)}
|
||||
```
|
||||
|
||||
- [ ] **Step 9: Replace chart JSX — Thread Count**
|
||||
|
||||
```tsx
|
||||
{threadData.length ? (
|
||||
<ThemedChart data={threadData} height={160} xDataKey="time"
|
||||
xTickFormatter={formatTime} yLabel="threads">
|
||||
<Line dataKey="threads" name="Threads" stroke={CHART_COLORS[0]}
|
||||
strokeWidth={2} dot={false} />
|
||||
</ThemedChart>
|
||||
) : (
|
||||
<EmptyState title="No data" description="No thread metrics available" />
|
||||
)}
|
||||
```
|
||||
|
||||
- [ ] **Step 10: Replace chart JSX — GC Pauses**
|
||||
|
||||
```tsx
|
||||
{gcData.length ? (
|
||||
<ThemedChart data={gcData} height={160} xDataKey="time"
|
||||
xTickFormatter={formatTime} yLabel="ms">
|
||||
<Area dataKey="gc" name="GC ms" stroke={CHART_COLORS[1]}
|
||||
fill={CHART_COLORS[1]} fillOpacity={0.1} strokeWidth={2} dot={false} />
|
||||
</ThemedChart>
|
||||
) : (
|
||||
<EmptyState title="No data" description="No GC metrics available" />
|
||||
)}
|
||||
```
|
||||
|
||||
- [ ] **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
|
||||
```
|
||||
Reference in New Issue
Block a user