name add-dashboard updated "2026-03-03T00:00:00.000Z" description Create a new customizable dashboard with its own chart registry, provider, and page. Use when adding dashboards like DRep or SPO dashboard. argument-hint ["DashboardName"] allowed-tools Read, Edit, Write, Glob, Grep, Bash
Add New Dashboard
Create a complete new dashboard instance with its own charts, state management, and page.
Arguments
$0 - Dashboard name in PascalCase (e.g., DRep, SPO, Voter)
Architecture Overview
Each dashboard is a self-contained module with these features:
Charts : Lazy-loaded chart components in a registry
Side Panel : Tabbed panel for Charts, Elements, Layout, Share
Text Elements : User-addable text labels
Page Margins : Draggable margin handles for width control
Multi-Select : Box selection and Ctrl+click for multiple elements
localStorage : All settings persist per-dashboard
src/
├── components/dashboards/
│ ├── shared/ # Reused across all dashboards
│ │ ├── DashboardProvider.tsx # State + localStorage (COPY & MODIFY)
│ │ ├── DashboardGrid.tsx # Canvas with selection (COPY & MODIFY)
│ │ ├── DashboardChartCard.tsx # Chart wrapper (REUSE AS-IS)
│ │ ├── DashboardTextElement.tsx # Text labels (REUSE AS-IS)
│ │ ├── DashboardSidePanel.tsx # Customization panel (COPY & MODIFY)
│ │ ├── DashboardMarginHandles.tsx # Margin controls (REUSE AS-IS)
│ │ └── chartTheme.ts # Theme colors (REUSE AS-IS)
│ │
│ ├── governance/ # Reference implementation
│ │ └── charts/
│ │ ├── index.tsx # CHART_REGISTRY
│ │ └── *.tsx # Chart components
│ │
│ ├── development_activity/ # Dev activity dashboard (real example)
│ │ └── charts/
│ │ ├── index.ts # CHART_REGISTRY
│ │ └── *.tsx # 13+ chart components
│ │
│ └── ${lowercase}/ # YOUR NEW DASHBOARD
│ ├── charts/
│ │ ├── index.tsx # CHART_REGISTRY for this dashboard
│ │ └── *.tsx # Chart components
│ ├── ${$0}DashboardProvider.tsx
│ ├── ${$0}DashboardGrid.tsx
│ ├── ${$0}DashboardSidePanel.tsx
│ └── index.ts # Barrel export
│
├── pages/
│ ├── adadev.tsx # Governance dashboard page
│ └── ${lowercase}-dashboard.tsx # YOUR NEW DASHBOARD PAGE
│
└── types/
└── ${lowercase}-dashboard.ts # Dashboard-specific types
Step 1: Create Dashboard Directory Structure
mkdir -p src/components/dashboards/${lowercase} /charts
Step 2: Create Chart Registry
Create src/components/dashboards/${lowercase}/charts/index.tsx:
import dynamic from "next/dynamic" ;
import { BarChart3 } from "lucide-react" ;
import { ChartSkeleton } from "@/components/dashboards/shared/ChartSkeleton" ;
import type { ChartDefinition } from "@/types/dashboard" ;
import { DEFAULT_ ${UPPERCASE }_CHART_LAYOUTS } from "@/types/${lowercase}-dashboard" ;
const Example ${$0}Chart = dynamic (
() => import ("./Example${$0}Chart" ).then ((mod ) => mod.Example$ {$0}Chart ),
{ loading : () => <ChartSkeleton /> , ssr : false }
);
export const ${UPPERCASE }_CHART_REGISTRY : ChartDefinition [] = [
{
id : "example-chart" ,
title : "Example Chart" ,
description : "Description of what this chart shows" ,
: ${$0} ,
: ,
: ${ }_CHART_LAYOUTS[ ],
: ,
},
];
get${$0} ( : ): | {
${ }_CHART_REGISTRY. ( chart. === id);
}
{ } ;
{ ${$0} };
Step 3: Create Dashboard Types
Create src/types/${lowercase}-dashboard.ts:
import type { ChartLayout , TextElement , PageMargins } from "./dashboard" ;
import { PAGE_MARGIN_CONSTRAINTS , DEFAULT_PAGE_MARGINS } from "./dashboard" ;
export type ${$0}ChartId = "example-chart" ;
export const ALL_ ${UPPERCASE }_CHART_IDS : ${$0}ChartId [] = ["example-chart" ];
export const DEFAULT_ ${UPPERCASE }_CHART_LAYOUTS : Record <${$0}ChartId , ChartLayout > = {
"example-chart" : { x : 0 , y : 0 , width : 380 , height : 320 },
};
export interface ${$0}DashboardConfig {
visibleCharts : ${$0}ChartId [];
chartOrder : ${$0} [];
: <${$0} , >;
: [];
: ;
: ;
}
${ } : ${$0} = {
: ${ }_CHART_IDS,
: ${ }_CHART_IDS,
: ${ }_CHART_LAYOUTS,
: [],
: ,
: ,
};
{ };
Step 4: Create Dashboard Provider
Create src/components/dashboards/${lowercase}/${$0}DashboardProvider.tsx:
Copy from shared/DashboardProvider.tsx and modify:
Change STORAGE_KEY to "${lowercase}-dashboard-config"
Update imports to use ${$0}ChartId, ${$0}DashboardConfig, etc.
Update default config imports
Rename context and hook
Key changes:
const STORAGE_KEY = "${lowercase}-dashboard-config" ;
import type {
${$0}ChartId ,
${$0}DashboardConfig ,
} from "@/types/${lowercase}-dashboard" ;
import {
DEFAULT_ ${UPPERCASE }_DASHBOARD_CONFIG,
DEFAULT_ ${UPPERCASE }_CHART_LAYOUTS,
ALL_ ${UPPERCASE }_CHART_IDS,
PAGE_MARGIN_CONSTRAINTS ,
} from "@/types/${lowercase}-dashboard" ;
interface ${$0}DashboardContextValue {
config : ${$0}DashboardConfig ;
mounted : boolean ;
isChartVisible : (chartId : ${$0}ChartId ) => boolean ;
toggleChartVisibility : (chartId : ${$0}ChartId ) => void ;
setVisibleCharts : (chartIds : ${$0}ChartId [] ) => void ;
getLayout : (chartId : ${$0}ChartId ) => ;
: ;
: ;
: ;
: ;
: ;
: ;
: ;
: ;
: { : ; ?: };
}
${$0} ({ children }) { ... }
use${$0} () { ... }
Step 5: Create Dashboard Grid
Create src/components/dashboards/${lowercase}/${$0}DashboardGrid.tsx:
Copy from shared/DashboardGrid.tsx and modify:
Import from your chart registry: ${UPPERCASE}_CHART_REGISTRY, get${$0}ChartById
Use your dashboard hook: use${$0}Dashboard
Update type references to ${$0}ChartId
Key Features to Preserve
Data Attributes (required for selection exclusion):
<DashboardChartCard data-chart-card ... />
<DashboardTextElement data-text-element ... />
Document-Level Selection Handler :
useEffect (() => {
const handleMouseDown = (e : MouseEvent ) => {
const target = e.target as HTMLElement ;
if (target.closest ("[data-chart-card], [data-text-element], [data-margin-handle], button, [role='button'], input, textarea, a" )) {
return ;
}
};
document .addEventListener ("mousedown" , handleMouseDown);
return () => document .removeEventListener ("mousedown" , handleMouseDown);
}, []);
Card Position Constraints (when margins change):
useEffect (() => {
if (!mounted || containerWidth < 400 ) return ;
for (const chartId of config.visibleCharts ) {
const layout = getLayout (chartId);
const maxX = Math .max (0 , containerWidth - layout.width );
if (layout.x > maxX) {
updateLayout (chartId, { x : snapToGrid (maxX) });
}
}
}, [containerWidth, mounted, ...]);
Step 6: Create Side Panel
Create src/components/dashboards/${lowercase}/${$0}DashboardSidePanel.tsx:
Copy from shared/DashboardSidePanel.tsx and modify:
Import your chart registry: get${$0}ChartById
Use your dashboard hook: use${$0}Dashboard
Side Panel Tabs
The panel has 4 tabs:
Tab Purpose Charts Toggle visibility, reorder via drag Elements Add/manage text labels Layout Page margin sliders Share Export/import config as base64
Step 7: Create Dashboard Page
Create src/pages/${lowercase}-dashboard.tsx:
import { useRef } from "react" ;
import Head from "next/head" ;
import { Card } from "@/components/ui/card" ;
import { GameLoader } from "@/components/ui/game-loader" ;
import { useTheme } from "@/lib/theme" ;
import {
${$0}DashboardProvider ,
${$0}DashboardGrid ,
${$0}DashboardSidePanel ,
use${$0}Dashboard ,
} from "@/components/dashboards/${lowercase}" ;
import { DashboardMarginHandles } from "@/components/dashboards/shared" ;
function ${$0}DashboardContent () {
const { activeTheme } = useTheme ();
const isGame = activeTheme.id === "game" ;
const { config, mounted } = use${$0}Dashboard ();
const gridAreaRef = useRef<HTMLDivElement >(null );
const isLoading = false ;
const error = null ;
hasData = ;
minPadding = ;
leftPadding = . (minPadding, config. . );
rightPadding = . (minPadding, config. . );
(
);
}
${$0} () {
(
<${$0} >
<${$0} />
</${$0} >
);
}
Step 8: Create First Chart
Create src/components/dashboards/${lowercase}/charts/Example${$0}Chart.tsx:
Use the /add-chart skill patterns but place in your dashboard's charts folder.
Step 9: Create Barrel Export
Create src/components/dashboards/${lowercase}/index.ts:
export { ${$0}DashboardProvider , use${$0}Dashboard } from "./${$0}DashboardProvider" ;
export { ${$0}DashboardGrid } from "./${$0}DashboardGrid" ;
export { ${$0}DashboardSidePanel } from "./${$0}DashboardSidePanel" ;
export { ${UPPERCASE }_CHART_REGISTRY, get${$0}ChartById } from "./charts" ;
Step 10: Add Navigation Link (Optional)
Update header navigation to include link to new dashboard.
Data Attributes Reference
These data attributes are used for selection box exclusion:
Attribute Element Purpose data-chart-cardChart wrapper Exclude from selection start data-text-elementText label Exclude from selection start data-margin-handleMargin lines Exclude from selection start
When dragging starts on elements with these attributes, the selection box won't activate.
Files Created Summary
File Purpose components/dashboards/${lowercase}/charts/index.tsxChart registry components/dashboards/${lowercase}/charts/Example${$0}Chart.tsxFirst chart components/dashboards/${lowercase}/${$0}DashboardProvider.tsxState management components/dashboards/${lowercase}/${$0}DashboardGrid.tsxGrid canvas with selection components/dashboards/${lowercase}/${$0}DashboardSidePanel.tsxCustomization panel components/dashboards/${lowercase}/index.tsBarrel export types/${lowercase}-dashboard.tsDashboard-specific types pages/${lowercase}-dashboard.tsxDashboard page
Gotchas: Graceful Degradation
Never gate all page content behind a single data endpoint's success. When a page uses multiple independent API endpoints, each section should handle errors independently:
Stats/summary cards: Show "--" when their endpoint fails
Lists/tables from separate endpoints: Render even if stats fail
Charts: Show empty state, not a full-page error
Use inline warnings instead of full-page blocking error cards
Anti-pattern:
{error && <FullPageError /> }
{data && !error && <AllContent /> }
Correct pattern:
{!isLoading && (
<>
{error && <InlineWarning /> }
{data && <DataDependentSection /> }
<IndependentSection /> {/* always renders */}
</>
)}
Verification Checklist
Dashboard page renders at /${lowercase}-dashboard
Charts display with correct theming (all 3 themes)
Drag and resize work correctly
Layout persists to localStorage (separate from governance)
Side panel opens with all 4 tabs working
Text elements can be added and edited
Page margins can be adjusted via handles and sliders
Box selection works for multi-select
Export/import config works
Page degrades gracefully when backend endpoints fail (no full-page errors)
Run npm run build - no TypeScript errors