원클릭으로
add-chart
Scaffold a new dashboard chart component with registry, types, and proper theme integration.
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
메뉴
Scaffold a new dashboard chart component with registry, types, and proper theme integration.
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
SOC 직업 분류 기준
Create a new customizable dashboard with its own chart registry, provider, and page. Use when adding dashboards like DRep or SPO dashboard.
Context window conservation rules. Invoke when approaching context limits or before large tasks.
Deep reflection on the skill learning system itself. Analyzes what's working, what's stale, and proposes structural improvements. The meta-skill.
End-of-session automation. Creates a journey and evolves skills based on session learnings.
Run the build and intelligently fix TypeScript errors with guardrails. Stops if fixes introduce more errors or the same error persists after 3 attempts.
Security and quality review of uncommitted changes. Checks for vulnerabilities, code smells, and best practice violations. Use before committing.
| name | add-chart |
| description | Scaffold a new dashboard chart component with registry, types, and proper theme integration. |
Create a new chart for a dashboard with proper theme integration for all 3 themes (light, dark, game).
For theming rules, Recharts gotchas, and i18n conventions, see _patterns.md.
$0 - Chart name in PascalCase (e.g., VoterTurnout)$1 - Chart type: bar, pie, line, area (default: bar)Create file at src/components/dashboards/{dashboard}/charts/${$0}Chart.tsx:
import { useMemo } from "react";
import { useAppSelector } from "@/store/hooks";
import { useTheme } from "@/lib/theme";
import { cn } from "@/lib/utils";
import { ChartSkeleton } from "@/components/dashboards/shared/ChartSkeleton";
import type { ChartProps } from "@/types/dashboard";
import {
getChartColors,
ChartTooltip,
chartCardClassName,
chartCardGameClassName,
} from "@/components/dashboards/shared/chartTheme";
// Import Recharts components based on chart type
// Bar: BarChart, Bar, XAxis, YAxis, Tooltip, ResponsiveContainer
// Pie: PieChart, Pie, Cell, Tooltip, ResponsiveContainer
// Line: LineChart, Line, XAxis, YAxis, Tooltip, ResponsiveContainer
export function ${$0}Chart({ isLoading, className }: ChartProps) {
const { actions } = useAppSelector((state) => state.governance);
const { activeTheme } = useTheme();
const chartColors = getChartColors(activeTheme.id);
const isGame = activeTheme.id === "game";
const data = useMemo(() => {
// TODO: Transform data for this chart
return [];
}, [actions]);
if (isLoading) return <ChartSkeleton className={className} />;
return (
<div className={cn(chartCardClassName, isGame && chartCardGameClassName, className)}>
<h3
className="text-sm font-semibold mb-4 dark:text-[#0bd1a2]"
style={isGame ? { color: chartColors.tooltipText } : undefined}
>
Chart Title
</h3>
<div className="flex-1 min-h-0">
<ResponsiveContainer width="100%" height="100%">
{/* Chart implementation */}
</ResponsiveContainer>
</div>
</div>
);
}
<XAxis
dataKey="name"
tick={{ fontSize: 11, fill: chartColors.axisText }}
axisLine={{ stroke: chartColors.axisLine }}
tickLine={false}
/>
contentStyle)<Tooltip content={<ChartTooltip themeId={activeTheme.id} />} />
// With formatters:
<Tooltip content={<ChartTooltip themeId={activeTheme.id} valueFormatter={(v) => `${v} proposals`} />} />
<Bar dataKey="value" fill={chartColors.primary} radius={0} />
<Pie data={data} dataKey="count" innerRadius="30%" outerRadius="55%" paddingAngle={2}
animationDuration={500} animationEasing="ease-in-out">
{data.map((entry, i) => (
<Cell key={i} fill={entry.fill} stroke={chartColors.tooltipBg} strokeWidth={2} />
))}
</Pie>
Pie overflow clipping: See _patterns.md Recharts section for the 3-layer fix.
<Line type="monotone" dataKey="value" stroke={chartColors.primary} strokeWidth={2}
dot={false} activeDot={{ r: 5, cursor: "pointer" }} />
{data.length === 0 ? (
<p className="text-sm text-muted-foreground">No data available</p>
) : (
<div className="flex-1 min-h-0"><ResponsiveContainer>...</ResponsiveContainer></div>
)}
chartColors)// Status: .active, .ratified, .enacted, .expired
// Votes: .yes, .no, .abstain
// Participation: .participationLow/MedLow/MedHigh/High
// Categorical: .palette[0..6] (7 colors per theme)
// Chrome: .axisLine, .axisText, .gridLine, .tooltipBg, .tooltipBorder, .tooltipText
// Accent: .primary, .primaryMuted
Edit src/components/dashboards/{dashboard}/charts/index.tsx:
// Add dynamic import
const ${$0}Chart = dynamic(
() => import("./${$0}Chart").then((mod) => mod.${$0}Chart),
{ loading: () => <ChartSkeleton />, ssr: false }
);
// Add to CHART_REGISTRY array
{
id: "${kebab-case}",
title: "${Human Title}",
description: "${Description}",
component: ${$0}Chart,
defaultVisible: true,
defaultLayout: DEFAULT_CHART_LAYOUTS["${kebab-case}"],
icon: BarChart3, // Options: BarChart3, PieChart, TrendingUp, Users, Vote, Gauge
},
// Add to exports
export { ${$0}Chart };
"${kebab-case}" to the relevant ChartId union typeALL_CHART_IDS arrayDEFAULT_CHART_LAYOUTS:"${kebab-case}": { x: 0, y: 1000, width: 380, height: 320 },
// All values must be multiples of 20. Sizes: Small 380x320, Medium 580x320, Large 780x320
Allow users to click chart elements to customize colors via side panel:
import { useChartColors } from "@/components/dashboards/shared/ChartColorsContext";
import { useDashboard } from "@/components/dashboards/shared/DashboardProvider";
const CHART_ID = "my-chart";
const { getColor } = useChartColors();
const { setColorPickerTarget } = useDashboard();
// Get color with fallback
const barColor = getColor(CHART_ID, "Active", chartColors.active);
// Click handler
const handleClick = (key: string, label: string) =>
setColorPickerTarget({ chartId: CHART_ID, chartTitle: "My Chart", elementKey: key, elementLabel: label });
// On elements
<Cell fill={getColor(CHART_ID, entry.key, entry.defaultFill)} onClick={() => handleClick(entry.key, entry.key)} style={{ cursor: "pointer" }} />
import { useTranslations } from "next-intl";
const t = useTranslations("charts");
<h3>{t("myChart.title")}</h3>
Add entries to all 7 locale files under src/messages/{locale}.json → "charts" namespace.
For charts fetching their own data (e.g., DRep charts), use SWR hooks from src/hooks/useDRepData.ts:
const { dreps, isLoading: drepsLoading } = useDRepList({ pageSize: 50 });
When data requires N+1 calls, create a server-side API route instead (see add-api-route skill).
useTheme() and getChartColors()chartCardClassName / chartCardGameClassNameChartTooltip (not contentStyle)chartColors, not hardcoded<ChartSkeleton />npm run build passes_patterns.md)