diff --git a/.planning/ROADMAP.md b/.planning/ROADMAP.md index 01f5b5e..9acaa53 100644 --- a/.planning/ROADMAP.md +++ b/.planning/ROADMAP.md @@ -30,11 +30,11 @@ Decimal phases appear between their surrounding integers in numeric order. 3. `PageShell` component renders a consistent page header with title, optional description, and CTA slot — and is importable from `components/shared/` 4. `StatCard` and `SummaryStrip` components render KPI cards (income, expenses, balance) with semantic color coding and variance badges — visible on the dashboard page 5. Skeleton loading components exist that mirror the real card and chart layout structure -**Plans**: TBD +**Plans**: 2 plans Plans: -- [ ] 01-01: TBD -- [ ] 01-02: TBD +- [ ] 01-01-PLAN.md — Install shadcn primitives (chart + collapsible), extend OKLCH color tokens, add i18n keys +- [ ] 01-02-PLAN.md — Build PageShell, StatCard, SummaryStrip, DashboardSkeleton and integrate into DashboardPage ### Phase 2: Dashboard Charts and Layout **Goal**: Deliver the full dashboard chart suite — donut, vertical bar, and horizontal bar — inside a responsive ChartPanel, with month navigation and memoized data derivations @@ -132,7 +132,7 @@ Phases execute in numeric order: 1 -> 2 -> 3 -> 4 | Phase | Plans Complete | Status | Completed | |-------|----------------|--------|-----------| -| 1. Design Foundation and Primitives | 0/TBD | Not started | - | +| 1. Design Foundation and Primitives | 0/2 | Planning complete | - | | 2. Dashboard Charts and Layout | 0/TBD | Not started | - | | 3. Collapsible Dashboard Sections | 0/TBD | Not started | - | | 4. Full-App Design Consistency | 0/TBD | Not started | - | diff --git a/.planning/phases/01-design-foundation-and-primitives/01-01-PLAN.md b/.planning/phases/01-design-foundation-and-primitives/01-01-PLAN.md new file mode 100644 index 0000000..70662ca --- /dev/null +++ b/.planning/phases/01-design-foundation-and-primitives/01-01-PLAN.md @@ -0,0 +1,221 @@ +--- +phase: 01-design-foundation-and-primitives +plan: 01 +type: execute +wave: 1 +depends_on: [] +files_modified: + - src/components/ui/chart.tsx + - src/components/ui/collapsible.tsx + - src/index.css + - src/i18n/en.json + - src/i18n/de.json +autonomous: true +requirements: + - UI-DESIGN-01 + - UI-DASH-01 + +must_haves: + truths: + - "shadcn chart primitive is installed and ChartContainer is importable from @/components/ui/chart" + - "shadcn collapsible primitive is installed and Collapsible is importable from @/components/ui/collapsible" + - "chart.tsx contains initialDimension={{ width: 320, height: 200 }} on ResponsiveContainer" + - "index.css @theme inline block contains semantic status tokens --color-over-budget and --color-on-budget" + - "index.css @theme inline block contains chart fill variants for all 6 category types" + - "Both en.json and de.json have matching new dashboard keys at parity" + artifacts: + - path: "src/components/ui/chart.tsx" + provides: "ChartContainer, ChartTooltip, ChartTooltipContent wrappers" + contains: "initialDimension" + - path: "src/components/ui/collapsible.tsx" + provides: "Collapsible, CollapsibleTrigger, CollapsibleContent" + - path: "src/index.css" + provides: "Extended OKLCH tokens with semantic status colors and chart fills" + contains: "--color-over-budget" + - path: "src/i18n/en.json" + provides: "English dashboard translation keys" + contains: "carryover" + - path: "src/i18n/de.json" + provides: "German dashboard translation keys" + contains: "carryover" + key_links: + - from: "src/index.css" + to: "Tailwind utility classes" + via: "@theme inline CSS variables" + pattern: "--color-(over-budget|on-budget|income-fill)" +--- + + +Install shadcn UI primitives (chart, collapsible), apply the Recharts v3 compatibility patch, extend the OKLCH color token system with richer chroma and semantic status tokens, and add new i18n keys for the dashboard redesign. + +Purpose: Establish the lowest-level design system building blocks that Plan 02 components and all subsequent phases depend on. Without tokens and primitives, no component can reference semantic colors or chart wrappers. + +Output: Patched chart.tsx, collapsible.tsx, extended index.css tokens, and parity-checked i18n keys in both languages. + + + +@/home/jlmak/.claude/get-shit-done/workflows/execute-plan.md +@/home/jlmak/.claude/get-shit-done/templates/summary.md + + + +@.planning/PROJECT.md +@.planning/ROADMAP.md +@.planning/STATE.md +@.planning/phases/01-design-foundation-and-primitives/01-RESEARCH.md + +@src/index.css +@src/i18n/en.json +@src/i18n/de.json +@components.json + + + + + + Task 1: Install shadcn primitives and patch chart.tsx + src/components/ui/chart.tsx, src/components/ui/collapsible.tsx + +1. Run `npx shadcn@latest add chart` to generate `src/components/ui/chart.tsx`. This installs the ChartContainer, ChartTooltip, and ChartTooltipContent wrappers around Recharts. + +2. Open the generated `src/components/ui/chart.tsx` and find the `ResponsiveContainer` element inside the `ChartContainer` component. Add the `initialDimension` prop to fix the Recharts v3 compatibility issue (shadcn-ui/ui#9892): + + BEFORE: + ```tsx + + ``` + AFTER: + ```tsx + + ``` + + NOTE: If the generated file ALREADY contains `initialDimension` (meaning PR #8486 has merged), skip the manual patch. + +3. Run `npx shadcn@latest add collapsible` to generate `src/components/ui/collapsible.tsx`. No post-install patch needed. + +4. Verify both files are importable by confirming `npm run build` passes. + +IMPORTANT: Do NOT install any npm packages manually. The shadcn CLI generates component files from the existing `radix-ui` and `recharts` packages already in package.json. + + + npm run build + + chart.tsx exists with initialDimension patch applied, collapsible.tsx exists, both are importable, build passes with zero errors. + + + + Task 2: Extend color tokens and add i18n keys + src/index.css, src/i18n/en.json, src/i18n/de.json + +**Part A: Extend color tokens in index.css** + +Open `src/index.css` and modify the `@theme inline` block. Keep ALL existing tokens unchanged. Add the following new tokens AFTER the existing `--color-chart-5` line and BEFORE `--radius`: + +1. Semantic status tokens (for budget comparison display): + ```css + /* Semantic Status Tokens */ + --color-over-budget: oklch(0.55 0.20 25); + --color-on-budget: oklch(0.50 0.17 155); + --color-budget-bar-bg: oklch(0.92 0.01 260); + ``` + +2. Chart fill variants (lighter versions of category colors for non-text chart fills at 3:1 minimum contrast): + ```css + /* Chart Fill Variants */ + --color-income-fill: oklch(0.68 0.19 155); + --color-bill-fill: oklch(0.65 0.19 25); + --color-variable-expense-fill: oklch(0.70 0.18 50); + --color-debt-fill: oklch(0.60 0.20 355); + --color-saving-fill: oklch(0.68 0.18 220); + --color-investment-fill: oklch(0.65 0.18 285); + ``` + +3. Update the existing 6 category color tokens to darker values for WCAG 4.5:1 text contrast against white (--color-card = oklch(1 0 0)): + ```css + --color-income: oklch(0.55 0.17 155); + --color-bill: oklch(0.55 0.17 25); + --color-variable-expense: oklch(0.58 0.16 50); + --color-debt: oklch(0.52 0.18 355); + --color-saving: oklch(0.55 0.16 220); + --color-investment: oklch(0.55 0.16 285); + ``` + +Do NOT modify any other existing tokens (background, foreground, primary, secondary, muted, accent, destructive, border, input, ring, sidebar-*). Do NOT modify the chart-1 through chart-5 tokens (they are used by shadcn chart config and will be updated separately in Phase 2 if needed). + +**Part B: Add i18n keys to en.json** + +Add the following keys to the `"dashboard"` section in `src/i18n/en.json`. Merge with existing keys (do not overwrite existing ones like "title", "totalIncome", "totalExpenses", "availableBalance", "expenseBreakdown", "noBudget"): + +```json +"dashboard": { + "title": "Dashboard", + "totalIncome": "Total Income", + "totalExpenses": "Total Expenses", + "netBalance": "Net Balance", + "availableBalance": "Available Balance", + "expenseBreakdown": "Expense Breakdown", + "noBudget": "No budget for this month. Create one to get started.", + "carryover": "Carryover", + "vsBudget": "vs budget", + "overBudget": "over budget", + "underBudget": "under budget", + "onTrack": "On track", + "loading": "Loading dashboard..." +} +``` + +New keys being added: "carryover", "vsBudget", "overBudget", "underBudget", "onTrack", "loading". + +**Part C: Add matching German i18n keys to de.json** + +Add the same new keys to the `"dashboard"` section in `src/i18n/de.json`: + +```json +"dashboard": { + "title": "Dashboard", + "totalIncome": "Gesamteinkommen", + "totalExpenses": "Gesamtausgaben", + "netBalance": "Nettobilanz", + "availableBalance": "Verfügbares Guthaben", + "expenseBreakdown": "Ausgabenübersicht", + "noBudget": "Kein Budget für diesen Monat. Erstelle eines, um loszulegen.", + "carryover": "Übertrag", + "vsBudget": "vs Budget", + "overBudget": "über Budget", + "underBudget": "unter Budget", + "onTrack": "Im Plan", + "loading": "Dashboard wird geladen..." +} +``` + +IMPORTANT: Both language files MUST be updated in the same commit. Verify key count parity: en.json and de.json should have the same number of total keys after changes. + + + npm run build && npm run lint + + index.css contains --color-over-budget, --color-on-budget, --color-budget-bar-bg, 6 chart fill variants, and darkened category text colors. en.json and de.json both contain the 6 new dashboard keys (carryover, vsBudget, overBudget, underBudget, onTrack, loading) at parity. + + + + + +1. `npm run build` passes (TypeScript type-check + Vite bundling) +2. `npm run lint` passes (ESLint) +3. `src/components/ui/chart.tsx` contains `initialDimension` +4. `src/components/ui/collapsible.tsx` exists and exports Collapsible components +5. `src/index.css` contains `--color-over-budget`, `--color-on-budget`, `--color-budget-bar-bg`, and 6 `*-fill` variants +6. Both en.json and de.json contain "carryover", "vsBudget", "overBudget", "underBudget", "onTrack", "loading" under dashboard section + + + +- Build passes with zero errors +- All shadcn primitives installed (chart.tsx with patch, collapsible.tsx) +- Color token system extended with semantic status tokens and two-tier category colors +- i18n keys at parity between en.json and de.json + + + +After completion, create `.planning/phases/01-design-foundation-and-primitives/01-01-SUMMARY.md` + diff --git a/.planning/phases/01-design-foundation-and-primitives/01-02-PLAN.md b/.planning/phases/01-design-foundation-and-primitives/01-02-PLAN.md new file mode 100644 index 0000000..8cc3b96 --- /dev/null +++ b/.planning/phases/01-design-foundation-and-primitives/01-02-PLAN.md @@ -0,0 +1,410 @@ +--- +phase: 01-design-foundation-and-primitives +plan: 02 +type: execute +wave: 2 +depends_on: + - 01-01 +files_modified: + - src/components/shared/PageShell.tsx + - src/components/dashboard/StatCard.tsx + - src/components/dashboard/SummaryStrip.tsx + - src/components/dashboard/DashboardSkeleton.tsx + - src/pages/DashboardPage.tsx +autonomous: true +requirements: + - UI-DASH-01 + - UI-DESIGN-01 + - UI-RESPONSIVE-01 + +must_haves: + truths: + - "PageShell renders a consistent page header with title, optional description, and optional CTA slot" + - "StatCard renders a KPI card with title, large formatted value, optional semantic color, and optional variance badge with directional icon" + - "SummaryStrip renders 3 StatCards in a responsive grid (1 col mobile, 2 cols tablet, 3 cols desktop)" + - "DashboardSkeleton mirrors the real summary card grid and chart card layout with pulse animations" + - "DashboardPage uses PageShell instead of inline h1 header" + - "DashboardPage uses SummaryStrip instead of inline SummaryCard components" + - "DashboardPage shows DashboardSkeleton during loading instead of returning null" + - "Balance card uses semantic text-on-budget/text-over-budget classes instead of hardcoded text-green-600/text-red-600" + artifacts: + - path: "src/components/shared/PageShell.tsx" + provides: "Consistent page header wrapper" + exports: ["PageShell"] + min_lines: 15 + - path: "src/components/dashboard/StatCard.tsx" + provides: "KPI display card with variance badge" + exports: ["StatCard"] + min_lines: 30 + - path: "src/components/dashboard/SummaryStrip.tsx" + provides: "Responsive row of 3 StatCards" + exports: ["SummaryStrip"] + min_lines: 20 + - path: "src/components/dashboard/DashboardSkeleton.tsx" + provides: "Skeleton loading placeholder for dashboard" + exports: ["DashboardSkeleton"] + min_lines: 20 + - path: "src/pages/DashboardPage.tsx" + provides: "Refactored dashboard page using new components" + contains: "PageShell" + key_links: + - from: "src/components/dashboard/SummaryStrip.tsx" + to: "src/components/dashboard/StatCard.tsx" + via: "import and composition" + pattern: "import.*StatCard" + - from: "src/pages/DashboardPage.tsx" + to: "src/components/shared/PageShell.tsx" + via: "import and wrapping" + pattern: "import.*PageShell" + - from: "src/pages/DashboardPage.tsx" + to: "src/components/dashboard/SummaryStrip.tsx" + via: "import replacing inline SummaryCard" + pattern: "import.*SummaryStrip" + - from: "src/pages/DashboardPage.tsx" + to: "src/components/dashboard/DashboardSkeleton.tsx" + via: "import replacing null loading state" + pattern: "import.*DashboardSkeleton" + - from: "src/pages/DashboardPage.tsx" + to: "src/index.css" + via: "semantic token classes" + pattern: "text-(on-budget|over-budget)" +--- + + +Build the shared components (PageShell, StatCard, SummaryStrip, DashboardSkeleton) and integrate them into DashboardPage, replacing the inline SummaryCard, null loading state, and hardcoded color classes. + +Purpose: Deliver the visual foundation components that all subsequent phases consume. After this plan, the dashboard has semantic KPI cards with variance badges, skeleton loading, and a consistent page header pattern ready for reuse across all 9 pages. + +Output: 4 new component files, refactored DashboardPage.tsx. + + + +@/home/jlmak/.claude/get-shit-done/workflows/execute-plan.md +@/home/jlmak/.claude/get-shit-done/templates/summary.md + + + +@.planning/PROJECT.md +@.planning/ROADMAP.md +@.planning/STATE.md +@.planning/phases/01-design-foundation-and-primitives/01-RESEARCH.md +@.planning/phases/01-design-foundation-and-primitives/01-01-SUMMARY.md + +@src/pages/DashboardPage.tsx +@src/components/ui/card.tsx +@src/components/ui/badge.tsx +@src/components/ui/skeleton.tsx +@src/lib/format.ts +@src/lib/palette.ts +@src/lib/types.ts +@src/i18n/en.json + + + + +From src/lib/types.ts: +```typescript +export type CategoryType = "income" | "bill" | "variable_expense" | "debt" | "saving" | "investment" +``` + +From src/lib/format.ts: +```typescript +export function formatCurrency(amount: number, currency?: string): string +``` + +From src/lib/palette.ts: +```typescript +export const categoryColors: Record +``` + +From src/components/ui/card.tsx: +```typescript +export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent } +``` + +From src/components/ui/badge.tsx: +```typescript +export { Badge, badgeVariants } +``` + +From src/components/ui/skeleton.tsx: +```typescript +export { Skeleton } +``` + +From src/hooks/useBudgets.ts: +```typescript +export function useBudgets(): { budgets: Budget[], loading: boolean, ... } +export function useBudgetDetail(id: string): { budget: Budget | null, items: BudgetItem[], loading: boolean } +``` + +From existing DashboardPage.tsx (lines 45-66) - the SummaryCard being REPLACED: +```typescript +interface SummaryCardProps { + title: string + value: string + valueClassName?: string +} +function SummaryCard({ title, value, valueClassName }: SummaryCardProps) { ... } +``` + +CSS tokens available from Plan 01 (src/index.css): +- `text-on-budget` (maps to --color-on-budget) +- `text-over-budget` (maps to --color-over-budget) +- `text-income` (maps to --color-income) +- `text-destructive` (maps to --color-destructive) + + + + + + + Task 1: Create PageShell, StatCard, SummaryStrip, and DashboardSkeleton components + src/components/shared/PageShell.tsx, src/components/dashboard/StatCard.tsx, src/components/dashboard/SummaryStrip.tsx, src/components/dashboard/DashboardSkeleton.tsx + +Create 4 new component files. Create directories `src/components/shared/` and `src/components/dashboard/` if they do not exist. + +**File 1: src/components/shared/PageShell.tsx** + +```tsx +interface PageShellProps { + title: string + description?: string + action?: React.ReactNode + children: React.ReactNode +} + +export function PageShell({ title, description, action, children }: PageShellProps) { + return ( +
+
+
+

{title}

+ {description && ( +

{description}

+ )} +
+ {action &&
{action}
} +
+ {children} +
+ ) +} +``` + +Key decisions: +- Named export (not default) per convention for shared components +- `text-2xl font-semibold tracking-tight` matches existing DashboardPage heading +- `action` is a ReactNode slot, not a button-specific prop +- No padding baked in -- AppLayout.tsx already provides `p-6` +- No i18n dependency -- title comes from the caller via `t()` at the page level + +**File 2: src/components/dashboard/StatCard.tsx** + +Follow the pattern from research (Pattern 2) exactly. Named export `StatCard`. + +Props interface: +```typescript +interface StatCardProps { + title: string + value: string + valueClassName?: string + variance?: { + amount: string + direction: "up" | "down" | "neutral" + label: string + } +} +``` + +Implementation: +- Import `Card`, `CardContent`, `CardHeader`, `CardTitle` from `@/components/ui/card` +- Import `TrendingUp`, `TrendingDown`, `Minus` from `lucide-react` +- Import `cn` from `@/lib/utils` +- Use `text-2xl font-bold tabular-nums tracking-tight` for the value (upgraded from existing `font-semibold` for more visual weight) +- Variance section renders a directional icon (size-3) + amount text + label in `text-xs text-muted-foreground` +- Do NOT import Badge -- the variance display uses inline layout, not a badge component + +**File 3: src/components/dashboard/SummaryStrip.tsx** + +Follow the pattern from research (Pattern 3). Named export `SummaryStrip`. + +Props interface: +```typescript +interface SummaryStripProps { + income: { value: string; budgeted: string } + expenses: { value: string; budgeted: string } + balance: { value: string; isPositive: boolean } + t: (key: string) => string +} +``` + +Implementation: +- Import `StatCard` from `./StatCard` +- Renders a `
` with 3 StatCards +- Income card: `title={t("dashboard.totalIncome")}`, `valueClassName="text-income"`, variance with direction "neutral" and label `t("budgets.budgeted")` +- Expenses card: `title={t("dashboard.totalExpenses")}`, `valueClassName="text-destructive"`, variance with direction "neutral" and label `t("budgets.budgeted")` +- Balance card: `title={t("dashboard.availableBalance")}`, `valueClassName={balance.isPositive ? "text-on-budget" : "text-over-budget"}`, no variance prop + +Note: The `t` function is passed as a prop to keep SummaryStrip as a presentational component that does not call `useTranslation()` internally. The parent (DashboardContent) already has `t` from `useTranslation()`. + +**File 4: src/components/dashboard/DashboardSkeleton.tsx** + +Follow the pattern from research (Pattern 4). Named export `DashboardSkeleton`. + +Implementation: +- Import `Skeleton` from `@/components/ui/skeleton` +- Import `Card`, `CardContent`, `CardHeader` from `@/components/ui/card` +- Renders a `
` with: + 1. Summary cards skeleton: `
` with 3 skeleton cards matching StatCard layout (Skeleton h-4 w-24 for title, Skeleton h-8 w-32 for value, Skeleton h-3 w-20 for variance) + 2. Chart area skeleton: `
` with 2 skeleton cards (Skeleton h-5 w-40 for chart title, Skeleton h-[240px] w-full rounded-md for chart area) + +This mirrors the real dashboard grid exactly so there is no layout shift when data loads. + +All 4 files use named exports. Follow import order convention: React first, third-party, internal types, internal utilities, components. + + + npm run build + + All 4 component files exist, export the correct named exports, follow project conventions, and build passes. PageShell accepts title/description/action/children. StatCard accepts title/value/valueClassName/variance. SummaryStrip renders 3 StatCards in responsive grid with semantic color classes. DashboardSkeleton mirrors the real layout structure. + + + + Task 2: Integrate new components into DashboardPage + src/pages/DashboardPage.tsx + +Refactor `src/pages/DashboardPage.tsx` to use the new shared components. This is a MODIFY operation -- preserve all existing logic (derived totals, pie chart, progress groups) while replacing the presentation layer. + +**Changes to make:** + +1. **Remove the inline SummaryCard component** (lines 45-66). Delete the entire `SummaryCardProps` interface and `SummaryCard` function. These are replaced by `StatCard`/`SummaryStrip`. + +2. **Add new imports** at the appropriate positions in the import order: + ```typescript + import { PageShell } from "@/components/shared/PageShell" + import { SummaryStrip } from "@/components/dashboard/SummaryStrip" + import { DashboardSkeleton } from "@/components/dashboard/DashboardSkeleton" + ``` + +3. **Replace loading states with DashboardSkeleton:** + - In `DashboardContent`: Replace `if (loading) return null` (line 76) with `if (loading) return ` + - In `DashboardPage`: Replace `if (loading) return null` (line 291) with: + ```tsx + if (loading) return ( + + + + ) + ``` + +4. **Replace hardcoded balance color** (lines 95-98): + - BEFORE: `const balanceColor = availableBalance >= 0 ? "text-green-600 dark:text-green-400" : "text-red-600 dark:text-red-400"` + - AFTER: `const balanceColor = availableBalance >= 0 ? "text-on-budget" : "text-over-budget"` + +5. **Replace hardcoded progress bar colors** (lines 219-221): + - BEFORE: `const barColor = group.overBudget ? "bg-red-500 dark:bg-red-400" : "bg-green-500 dark:bg-green-400"` + - AFTER: `const barColor = group.overBudget ? "bg-over-budget" : "bg-on-budget"` + +6. **Replace hardcoded progress text color** (lines 235-239): + - BEFORE: `group.overBudget ? "text-red-600 dark:text-red-400" : "text-muted-foreground"` + - AFTER: `group.overBudget ? "text-over-budget" : "text-muted-foreground"` + +7. **Replace inline summary cards with SummaryStrip** in DashboardContent's return JSX. Replace the `
` block (lines 135-149) with: + ```tsx + i.category?.type === "income").reduce((sum, i) => sum + i.budgeted_amount, 0), + currency + ), + }} + expenses={{ + value: formatCurrency(totalExpenses, currency), + budgeted: formatCurrency( + items.filter((i) => i.category?.type !== "income").reduce((sum, i) => sum + i.budgeted_amount, 0), + currency + ), + }} + balance={{ + value: formatCurrency(availableBalance, currency), + isPositive: availableBalance >= 0, + }} + t={t} + /> + ``` + + To avoid recomputing budgeted totals inline, derive them alongside the existing totalIncome/totalExpenses calculations: + ```typescript + const budgetedIncome = items + .filter((i) => i.category?.type === "income") + .reduce((sum, i) => sum + i.budgeted_amount, 0) + + const budgetedExpenses = items + .filter((i) => i.category?.type !== "income") + .reduce((sum, i) => sum + i.budgeted_amount, 0) + ``` + +8. **Replace the page header with PageShell** in the `DashboardPage` component's return. Replace: + ```tsx +
+
+

{t("dashboard.title")}

+
+ {/* content */} +
+ ``` + With: + ```tsx + + {/* content */} + + ``` + +**What to preserve:** +- All imports for Recharts (PieChart, Pie, Cell, ResponsiveContainer, Tooltip) +- The `EXPENSE_TYPES` constant +- The `currentMonthStart` helper +- The `DashboardContent` component structure (budgetId prop, hooks, derived totals, pie chart, progress groups) +- The `QuickAddPicker` usage +- The entire pie chart + legend section +- The entire category progress section (but with updated color classes) +- The no-budget empty state with Link to /budgets + +**What to remove:** +- The `SummaryCardProps` interface and `SummaryCard` function component +- The hardcoded `text-green-600`, `text-red-600`, `bg-red-500`, `bg-green-500` color classes +- The `if (loading) return null` patterns (both in DashboardContent and DashboardPage) +- The inline `
` header + + + npm run build && npm run lint + + DashboardPage imports and uses PageShell, SummaryStrip, and DashboardSkeleton. No more inline SummaryCard component. Loading states show skeleton instead of null. All hardcoded green/red color classes replaced with semantic token classes (text-on-budget, text-over-budget, bg-on-budget, bg-over-budget). Build and lint pass. + + + + + +1. `npm run build && npm run lint` passes +2. `src/components/shared/PageShell.tsx` exports `PageShell` +3. `src/components/dashboard/StatCard.tsx` exports `StatCard` +4. `src/components/dashboard/SummaryStrip.tsx` exports `SummaryStrip` and imports `StatCard` +5. `src/components/dashboard/DashboardSkeleton.tsx` exports `DashboardSkeleton` +6. `src/pages/DashboardPage.tsx` imports PageShell, SummaryStrip, DashboardSkeleton +7. No occurrences of `text-green-600`, `text-red-600`, `bg-red-500`, `bg-green-500` remain in DashboardPage.tsx +8. No occurrences of `SummaryCard` remain in DashboardPage.tsx +9. No `return null` for loading states in DashboardPage.tsx + + + +- All 4 new component files exist and are well-typed +- DashboardPage uses PageShell for header, SummaryStrip for KPI cards, DashboardSkeleton for loading +- Zero hardcoded green/red color values in DashboardPage +- Build and lint pass cleanly +- Summary cards display in responsive grid (1/2/3 columns by breakpoint) + + + +After completion, create `.planning/phases/01-design-foundation-and-primitives/01-02-SUMMARY.md` +