This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
- Enter plan mode for non-trivial tasks (3+ steps or architectural decisions)
- If something goes sideways, STOP and re-plan — don't keep pushing
- Use subagents to keep main context clean; one task per subagent
- After corrections: update
tasks/lessons.mdwith the pattern - Never mark a task complete without proving it works (run tests, check logs)
- Autonomous bug fixing: just fix it, don't ask for hand-holding
- Write plan to
tasks/todo.mdwith checkable items - Check in before starting implementation
- Mark items complete as you go; capture lessons in
tasks/lessons.md
- Simplicity First: Make every change as simple as possible. Minimal code impact.
- No Laziness: Find root causes. No temporary fixes. Senior developer standards.
- Minimal Impact: Only touch what's necessary. Avoid introducing bugs.
Vue Charts (vccs) — An unofficial Vue 3 port of Recharts. Composable charting components built with Vue 3 Composition API + JSX/TSX.
- When porting, refer to React source for behavior parity
- Monorepo:
vccs(library) +play(Nuxt playground) +docs(Nuxt docs site), managed by pnpm workspaces
pnpm install # Install dependencies
pnpm dev # Watch mode
pnpm build # Build library (alias for --filter vccs build)
pnpm test # Run tests
pnpm test:coverage # Tests with coverage
pnpm storybook # Storybook
pnpm play # Playground
pnpm docs # Docs site (Nuxt 3, port 3001)
pnpm pub:release # Publish
# Run specific test
pnpm test packages/vue/src/chart/__tests__/AreaChart.spec.tsxCI (.github/workflows/test.yml): triggers on PRs to main; runs pnpm install --frozen-lockfile → pnpm --filter vccs build → pnpm test on Node 20 / ubuntu-latest.
packages/vue/src/ # Main library source (vccs)
├── cartesian/ # Area, Bar, Line, Scatter, Axis, Brush, CartesianGrid, ZAxis; funnel/ subdirectory
├── polar/ # Pie, Radar, RadialBar, PolarGrid, PolarAngleAxis, PolarRadiusAxis
├── chart/ # Chart containers (AreaChart, BarChart, LineChart, ComposedChart, FunnelChart, etc.)
├── components/ # Legend, Tooltip, Text, Label, Cell
├── container/ # ResponsiveContainer, Surface, Layer
├── shape/ # Rectangle, Symbols, Dot, Sector, Cross, Curve, Trapezoid
├── state/ # Redux store, slices, middleware, selectors
├── animation/ # Animate component, motion-v utilities
├── context/ # provide/inject context providers
├── hooks/ # Shared composition hooks
├── storybook/ # Stories
├── types/ # Shared type definitions
├── utils/ # Utility functions
└── index.ts # Public API
docs/ # Documentation site (Nuxt 3, Docus framework)
playground/nuxt/ # Nuxt 3 playground (Tailwind v4, shadcn-nuxt)
- Components:
defineComponent+ JSX (not SFC) - State: Redux Toolkit via
@reduxjs/vue-redux— one store per chart (createRechartsStore) - Context:
provide/injectfor parent-child communication - Chart Factory:
generateCategoricalChart()creates chart containers - Animation:
motion-vwithAnimatewrapper - Events: Redux middleware — mouse events use
createListenerMiddleware; external/keyboard/touch use plain synchronousMiddleware - Build output: ESM-only (
preserveModules: true);minify: false— Rolldown's minifier renames variables colliding with Vue'sh
- Components: PascalCase; Directories: kebab-case; Hooks:
useprefix; Types:Propssuffix - Type files:
type.ts; Tests:__tests__/*.spec.tsx; Stories:__stories__/*.stories.tsx
export const Component = defineComponent<PropsWithSVG>({
name: 'Component',
props: ComponentVueProps,
inheritAttrs: false,
slots: Object as SlotsType<Slots>,
setup(props, { attrs, slots }) {
useSetupGraphicalItem(props, 'itemType')
const { ...data } = useComponentHook(props, attrs)
return () => (/* JSX */)
},
})Volar slot type preservation (for compiled .d.ts):
const _Component = defineComponent<PropsWithSVG>({ /* ... */ })
export const Component = _Component as typeof _Component & {
new (): { $slots: ComponentSlots }
}export const ComponentVueProps = {
dataKey: { type: [String, Number, Function] as PropType<DataKey<any>>, required: true },
fill: { type: String, default: undefined },
}
export type ComponentPropsWithSVG = WithSVGProps<VuePropsToType<typeof ComponentVueProps>>@/→packages/vue/src/- Prefer
import typefor type-only imports
- React
useState/useEffect→ Vueref/watch; Context →provide/inject - React
useMemo/useCallback→ Vuecomputed/ plain functions - React JSX → Vue JSX (
classnotclassName, kebab-case SVG attrs)
Customization uses named slots: shape, activeBar, dot, activeDot, label, content, cursor, tick, horizontal, vertical.
- Always
toRaw(entry)before passing to D3 scale functions (Vue Proxy breaks D3)
Three-tier z-ordering: cursor → graphical → label (via Surface.vue).
- Chase pattern (Bar, Line, Scatter, Radar, RadialBar):
previousData+ incrementinganimationIdasAnimatekey - Area: uses
ClipRectfor initial entrance + directmotion-vanimate()for data changes (NOTAnimatewrapper) Animaterespectsprefers-reduced-motionviausePreferredReducedMotion()useIsAnimatinghook: shared by Bar, Line, Radar, RadialBar, Area, Funnel — pass getter:useIsAnimating(() => props.isAnimationActive)
- Created via
generateCategoricalChart({ defaultTooltipEventType: 'item' }) - Registers via
SetPolarGraphicalItem(NOTuseSetupGraphicalItem) - Coordinate calculation:
xusesleftoffset,yusestopoffset — do NOT swap (was a ported bug fix) - Animation: motion-v
transition: AnimationOptions— NOT legacy RechartsanimationBegin/animationDuration/animationEasing
#contentslot:<Tooltip><template #content="{ active, payload, label }">...</template></Tooltip>- Use destructured props, NOT
v-bind="tooltipProps"—@antfu/eslint-configauto-fix stripsv-bindspread TooltipIndextype isstring | null(NOT a number)
isAnimationActive={false}for deterministic renderingmockGetBoundingClientRect({ width, height })inbeforeEach- Tests as render functions:
render(() => <Component />) - Public API imports from
@/index; internal imports use direct paths - CSS class selectors:
.v-charts-{component}pattern (e.g..v-charts-line-curve,.v-charts-bar-rectangle,.v-charts-funnel,.v-charts-trapezoid) - ResponsiveContainer uses
vcharts-prefix (notv-charts-):.vcharts-responsive-container - Tooltip hover:
fireEvent(chart, new MouseEvent('mousemove', {...}))on.v-charts-wrapper+ 2xnextTick();defaultIndexrequires 3xnextTick() - ResizeObserver:
MockResizeObserverwithtrigger(width, height)method - Animation in JSDOM:
motion-vuses RAF — value stays atfromwhenisActive=true; snaps totowhenisActive=false
- Story titles must match Recharts conventions
- Clone array data in
render:data={[...data1]}(Vue proxy errors)
- Color palette:
#f97316orange,#14b8a6teal,#f59e0bamber,#06b6d4cyan - Always add
:cursor="false"to<Tooltip>in docs demos - MDC syntax:
::chart-demo{src="..."}::to embed live demos - Tailwind v4 syntax:
border-(--color-border)(NOT v3border-[--color-border])
| Library | Purpose |
|---|---|
@reduxjs/toolkit + @reduxjs/vue-redux |
Chart state management |
motion-v |
SVG animations (external peer dep) |
victory-vendor |
D3 math/scale utilities |
lodash-es / es-toolkit |
Utility functions |
@vueuse/core |
Vue composition utilities |
Add project-specific notes here. This section is never auto-modified.