diff --git a/src/renderer/components/dashboard/ChartSection.tsx b/src/renderer/components/dashboard/ChartSection.tsx
new file mode 100644
index 00000000..ec11a719
--- /dev/null
+++ b/src/renderer/components/dashboard/ChartSection.tsx
@@ -0,0 +1,97 @@
+import { useMemo } from "react";
+import { motion } from "framer-motion";
+import type { RunRecord, CostRecord, AgentRecord } from "@shared/types";
+import { useReducedMotion } from "../ui";
+import {
+ aggregateRunsByDay,
+ aggregateCostsByDay,
+ aggregateAgentWorkload,
+} from "./charts/chart-utils";
+import { ChartCard } from "./charts/ChartCard";
+import { RunActivityChart } from "./charts/RunActivityChart";
+import { SuccessRateChart } from "./charts/SuccessRateChart";
+import { CostTrendChart } from "./charts/CostTrendChart";
+import { AgentWorkloadChart } from "./charts/AgentWorkloadChart";
+import { BudgetUtilization } from "./charts/BudgetUtilization";
+
+interface ChartSectionProps {
+ runs: RunRecord[];
+ costs: CostRecord[];
+ agents: AgentRecord[];
+ companyId: string;
+}
+
+export function ChartSection({ runs, costs, agents, companyId }: ChartSectionProps) {
+ const reducedMotion = useReducedMotion();
+
+ const dailyRuns = useMemo(() => aggregateRunsByDay(runs, 14), [runs]);
+ const dailyCosts = useMemo(() => aggregateCostsByDay(costs, 14), [costs]);
+ const agentWorkload = useMemo(
+ () => aggregateAgentWorkload(runs, agents),
+ [runs, agents],
+ );
+
+ const hasRunData = dailyRuns.some((d) => d.total > 0);
+ const hasCostData = dailyCosts.some((d) => d.total > 0);
+
+ const content = (
+
+ {/* Section header */}
+
+ Performance
+
+
+ {/* 2-column chart grid */}
+
+
s + d.total, 0)} total runs over 14 days`}
+ isEmpty={!hasRunData}
+ >
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {/* Budget Utilization section */}
+
+ Budget Utilization
+
+
+
+ );
+
+ if (reducedMotion) {
+ return content;
+ }
+
+ return (
+
+ {content}
+
+ );
+}
diff --git a/src/renderer/components/dashboard/Dashboard.tsx b/src/renderer/components/dashboard/Dashboard.tsx
index 90762256..23dc7a11 100644
--- a/src/renderer/components/dashboard/Dashboard.tsx
+++ b/src/renderer/components/dashboard/Dashboard.tsx
@@ -27,6 +27,7 @@ import { MetricCard } from "./MetricsGrid";
import { ActivityFeed } from "./ActivityFeed";
import { RecoveryBanner } from "./RecoveryBanner";
import { BudgetAlertBanner } from "./BudgetAlertBanner";
+import { ChartSection } from "./ChartSection";
export interface DashboardProps {
companyName?: string;
@@ -132,6 +133,9 @@ export function Dashboard(props: DashboardProps) {
/>
+ {/* Performance Charts */}
+ {companyId ? : null}
+
{/* Needs Attention — moved to top, only shows when items exist */}
{hasAttentionItems ? (
diff --git a/src/renderer/components/dashboard/charts/AgentWorkloadChart.tsx b/src/renderer/components/dashboard/charts/AgentWorkloadChart.tsx
new file mode 100644
index 00000000..ccd4cffa
--- /dev/null
+++ b/src/renderer/components/dashboard/charts/AgentWorkloadChart.tsx
@@ -0,0 +1,52 @@
+import {
+ BarChart,
+ Bar,
+ XAxis,
+ YAxis,
+ CartesianGrid,
+ Tooltip,
+ ResponsiveContainer,
+} from "recharts";
+import type { AgentWorkloadData } from "./chart-utils";
+import { ChartTooltip } from "./ChartTooltip";
+
+interface AgentWorkloadChartProps {
+ data: AgentWorkloadData[];
+ reducedMotion: boolean;
+}
+
+export function AgentWorkloadChart({ data, reducedMotion }: AgentWorkloadChartProps) {
+ return (
+
+
+
+
+
+ } />
+
+
+
+ );
+}
diff --git a/src/renderer/components/dashboard/charts/BudgetUtilization.tsx b/src/renderer/components/dashboard/charts/BudgetUtilization.tsx
new file mode 100644
index 00000000..8c2a41fd
--- /dev/null
+++ b/src/renderer/components/dashboard/charts/BudgetUtilization.tsx
@@ -0,0 +1,87 @@
+import { useMemo } from "react";
+import type { AgentRecord } from "@shared/types";
+
+interface BudgetUtilizationProps {
+ companyId: string;
+ agents: AgentRecord[];
+}
+
+interface BudgetRow {
+ id: string;
+ name: string;
+ spent: number;
+ budget: number;
+ pct: number;
+}
+
+function getBarColor(pct: number): string {
+ if (pct >= 100) return "bg-[color:var(--danger)]";
+ if (pct >= 80) return "bg-[color:var(--warn)]";
+ return "bg-[color:var(--success)]";
+}
+
+export function BudgetUtilization({ companyId: _companyId, agents }: BudgetUtilizationProps) {
+ const rows = useMemo(() => {
+ return agents
+ .filter((a) => a.budgetMonthlyUsd > 0)
+ .map((a) => ({
+ id: a.id,
+ name: a.name,
+ spent: a.spentMonthlyUsd,
+ budget: a.budgetMonthlyUsd,
+ pct: Math.round((a.spentMonthlyUsd / a.budgetMonthlyUsd) * 100),
+ }))
+ .sort((a, b) => a.name.localeCompare(b.name));
+ }, [agents]);
+
+ if (rows.length === 0) {
+ return (
+
+
+
+ No budget policies configured
+
+
+ Set budget policies in Settings to track utilization here.
+
+
+
+ );
+ }
+
+ return (
+
+
+ {rows.map((row) => (
+
+
+
+ {row.name}
+
+
+ {row.pct}%
+
+
+
+
+ {/* 80% warning marker */}
+
+
+
+ ))}
+
+
+ );
+}
diff --git a/src/renderer/components/dashboard/charts/CostTrendChart.tsx b/src/renderer/components/dashboard/charts/CostTrendChart.tsx
new file mode 100644
index 00000000..41f69fe8
--- /dev/null
+++ b/src/renderer/components/dashboard/charts/CostTrendChart.tsx
@@ -0,0 +1,57 @@
+import {
+ AreaChart,
+ Area,
+ XAxis,
+ YAxis,
+ CartesianGrid,
+ Tooltip,
+ ResponsiveContainer,
+} from "recharts";
+import type { DailyCostData } from "./chart-utils";
+import { formatShortDate } from "./chart-utils";
+import { ChartTooltip } from "./ChartTooltip";
+
+interface CostTrendChartProps {
+ data: DailyCostData[];
+ reducedMotion: boolean;
+}
+
+export function CostTrendChart({ data, reducedMotion }: CostTrendChartProps) {
+ return (
+
+
+
+
+ "$" + val.toFixed(0)}
+ tick={{ fontSize: 11, fill: "var(--muted)" }}
+ axisLine={false}
+ tickLine={false}
+ />
+ } />
+
+
+
+ );
+}
diff --git a/src/renderer/components/dashboard/charts/RunActivityChart.tsx b/src/renderer/components/dashboard/charts/RunActivityChart.tsx
new file mode 100644
index 00000000..2551b463
--- /dev/null
+++ b/src/renderer/components/dashboard/charts/RunActivityChart.tsx
@@ -0,0 +1,57 @@
+import {
+ AreaChart,
+ Area,
+ XAxis,
+ YAxis,
+ CartesianGrid,
+ Tooltip,
+ ResponsiveContainer,
+} from "recharts";
+import type { DailyRunData } from "./chart-utils";
+import { formatShortDate } from "./chart-utils";
+import { ChartTooltip } from "./ChartTooltip";
+
+interface RunActivityChartProps {
+ data: DailyRunData[];
+ reducedMotion: boolean;
+}
+
+export function RunActivityChart({ data, reducedMotion }: RunActivityChartProps) {
+ return (
+
+
+
+
+
+ } />
+
+
+
+ );
+}
diff --git a/src/renderer/components/dashboard/charts/SuccessRateChart.tsx b/src/renderer/components/dashboard/charts/SuccessRateChart.tsx
new file mode 100644
index 00000000..2e6d4bde
--- /dev/null
+++ b/src/renderer/components/dashboard/charts/SuccessRateChart.tsx
@@ -0,0 +1,66 @@
+import { useMemo } from "react";
+import {
+ LineChart,
+ Line,
+ XAxis,
+ YAxis,
+ CartesianGrid,
+ Tooltip,
+ ResponsiveContainer,
+} from "recharts";
+import type { DailyRunData } from "./chart-utils";
+import { formatShortDate } from "./chart-utils";
+import { ChartTooltip } from "./ChartTooltip";
+
+interface SuccessRateChartProps {
+ data: DailyRunData[];
+ reducedMotion: boolean;
+}
+
+export function SuccessRateChart({ data, reducedMotion }: SuccessRateChartProps) {
+ const rateData = useMemo(
+ () =>
+ data.map((d) => ({
+ date: d.date,
+ rate: d.total > 0 ? Math.round((d.succeeded / d.total) * 100) : 0,
+ })),
+ [data],
+ );
+
+ return (
+
+
+
+
+ `${val}%`}
+ tick={{ fontSize: 11, fill: "var(--muted)" }}
+ axisLine={false}
+ tickLine={false}
+ />
+ } />
+
+
+
+ );
+}