`) is replaced by `PageShell` usage
+
+### Pattern 2: StatCard -- KPI Display Unit
+
+**What:** A single KPI card that displays a label, large formatted value, semantic color coding, and an optional variance badge.
+**When to use:** Summary cards on the dashboard (income, expenses, balance). May also be used on BudgetDetailPage summary in Phase 4.
+
+```typescript
+// src/components/dashboard/StatCard.tsx
+import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
+import { Badge } from "@/components/ui/badge"
+import { TrendingUp, TrendingDown, Minus } from "lucide-react"
+import { cn } from "@/lib/utils"
+
+interface StatCardProps {
+ title: string
+ value: string
+ valueClassName?: string
+ variance?: {
+ amount: string
+ direction: "up" | "down" | "neutral"
+ label: string
+ }
+}
+
+export function StatCard({ title, value, valueClassName, variance }: StatCardProps) {
+ return (
+
+
+
+ {title}
+
+
+
+
+ {value}
+
+ {variance && (
+
+ {variance.direction === "up" && }
+ {variance.direction === "down" && }
+ {variance.direction === "neutral" && }
+
+ {variance.amount} {variance.label}
+
+
+ )}
+
+
+ )
+}
+```
+
+**Key decisions:**
+- Extends the existing `SummaryCard` pattern from `DashboardPage.tsx` (lines 45-66)
+- Adds `variance` prop for delta arrows/badges (differentiator from FEATURES.md)
+- Uses `text-2xl font-bold` (upgraded from existing `font-semibold`) for more visual weight
+- `tabular-nums tracking-tight` ensures financial numbers align properly
+- Lucide icons (`TrendingUp`, `TrendingDown`) supplement color for accessibility (Pitfall 4)
+
+### Pattern 3: SummaryStrip -- KPI Cards Row
+
+**What:** A responsive grid row of 3 `StatCard` instances (income, expenses, balance).
+
+```typescript
+// src/components/dashboard/SummaryStrip.tsx
+import { StatCard } from "./StatCard"
+
+interface SummaryStripProps {
+ income: { value: string; budgeted: string }
+ expenses: { value: string; budgeted: string }
+ balance: { value: string; isPositive: boolean; carryover?: string }
+ t: (key: string) => string
+}
+
+export function SummaryStrip({ income, expenses, balance, t }: SummaryStripProps) {
+ return (
+
+
+
+
+
+ )
+}
+```
+
+**Key decisions:**
+- Grid: `grid-cols-1` on mobile, `sm:grid-cols-2` on tablet, `lg:grid-cols-3` on desktop
+- Balance card uses semantic token classes `text-on-budget` / `text-over-budget` (not hardcoded `text-green-600` / `text-red-600`)
+- Income card uses `text-income` (maps to `--color-income` CSS variable)
+
+### Pattern 4: Skeleton Loading Components
+
+**What:** Skeleton placeholders that mirror the real card and chart layout structure so the page does not flash blank during loading.
+
+```typescript
+// src/components/dashboard/DashboardSkeleton.tsx
+import { Skeleton } from "@/components/ui/skeleton"
+import { Card, CardContent, CardHeader } from "@/components/ui/card"
+
+export function DashboardSkeleton() {
+ return (
+
+ {/* Summary cards skeleton */}
+
+ {Array.from({ length: 3 }).map((_, i) => (
+
+
+
+
+
+
+
+
+
+ ))}
+
+ {/* Chart area skeleton */}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ )
+}
+```
+
+**Key decisions:**
+- Mirrors the real dashboard grid layout exactly (3-col summary cards, 2-col chart area)
+- Uses existing `Skeleton` from `components/ui/skeleton.tsx` (already installed)
+- Card structure matches the real `StatCard` layout so there is no layout shift when data loads
+- Chart skeleton height matches the `ResponsiveContainer height={240}` used in the existing pie chart
+
+### Anti-Patterns to Avoid
+
+- **Hardcoding hex/oklch values in components:** Always use CSS variable references (`var(--color-income)`) or Tailwind semantic classes (`text-income`). The `palette.ts` file maps CategoryType to `var(--color-X)`.
+- **Using `text-green-600` / `text-red-600` for budget status:** Replace with semantic tokens `--color-on-budget` and `--color-over-budget` that are verified for WCAG 4.5:1 contrast. The existing codebase uses hardcoded Tailwind green/red in 4 places (DashboardPage.tsx lines 96-98, 220-221; BudgetDetailPage.tsx lines 168-173, 443-449).
+- **Modifying hooks or lib files:** All changes are in `components/`, `pages/`, `index.css`, and `i18n/` only. Hooks and library files are read-only during this milestone.
+- **Adding i18n keys to only one language file:** Every new key MUST be added to both `en.json` and `de.json` in the same commit. The i18next config uses `fallbackLng: 'en'` which silently hides missing German keys.
+
+## Don't Hand-Roll
+
+| Problem | Don't Build | Use Instead | Why |
+|---------|-------------|-------------|-----|
+| Chart theme wrappers | Custom `ResponsiveContainer` wrapper | shadcn `chart.tsx` `ChartContainer` + `ChartConfig` | Provides CSS-variable-aware theming, consistent tooltips, and proper SSR dimensions |
+| Collapsible sections | `display:none` toggle or JS height animation | Radix `Collapsible` via `npx shadcn@latest add collapsible` | Handles `height: 0 -> auto` animation via `--radix-collapsible-content-height` CSS variable; avoids layout thrash |
+| Loading skeletons | Custom shimmer/pulse animation | shadcn `Skeleton` component (already installed) | Provides `animate-pulse rounded-md bg-accent` -- consistent with design system |
+| WCAG contrast checking | Manual hex comparison | OddContrast (oddcontrast.com) or Atmos (atmos.style/contrast-checker) | Both accept OKLCH input directly; compute WCAG 2 ratio |
+| Currency formatting | Custom number formatting | Existing `formatCurrency()` from `src/lib/format.ts` | Already handles locale-aware Intl.NumberFormat with EUR/USD |
+| Color mapping | Inline color lookup objects | Existing `categoryColors` from `src/lib/palette.ts` | Single source of truth; returns `var(--color-X)` strings |
+
+## Common Pitfalls
+
+### Pitfall 1: chart.tsx Recharts v3 Incompatibility
+
+**What goes wrong:** Running `npx shadcn@latest add chart` generates a `chart.tsx` that does not include `initialDimension` on `ResponsiveContainer`. With Recharts 3.8.0, this causes `width(-1) and height(-1)` console warnings and charts may render at zero dimensions.
+**Why it happens:** The official shadcn chart.tsx PR #8486 for Recharts v3 is not yet merged (as of March 2026). The CLI still generates v2-compatible code.
+**How to avoid:** Immediately after running the CLI command, open `src/components/ui/chart.tsx`, find the `ResponsiveContainer` inside `ChartContainer`, and add `initialDimension={{ width: 320, height: 200 }}`.
+**Warning signs:** Console warning `"The width(-1) and height(-1) of chart should be greater than 0"`. Charts render as invisible/zero-height.
+
+### Pitfall 2: Color Accessibility Regression During "Rich Visual" Overhaul
+
+**What goes wrong:** Bumping OKLCH chroma from 0.14 to 0.18+ makes colors more vivid but may push them below WCAG 4.5:1 contrast against the white card background (L=1.0).
+**Why it happens:** Higher chroma at the same lightness can reduce relative luminance difference against white. The existing `text-green-600` (`#16a34a`) is borderline at 4.5:1. The six category colors all cluster at similar lightness (L ~0.65-0.72), making them hard to distinguish for colorblind users.
+**How to avoid:**
+1. Run every proposed color pair through OddContrast (oddcontrast.com) using OKLCH input
+2. For text colors, target at minimum 4.5:1 contrast ratio against `--color-card` (oklch(1 0 0) = white)
+3. For non-text UI elements (chart slices, progress bars), target 3:1 minimum (WCAG 2.1 SC 1.4.11)
+4. Vary OKLCH lightness across categories (range 0.55-0.75), not just hue
+5. Supplement color with icons for all status indicators (Pitfall 4 from research)
+**Warning signs:** Colors look vivid on developer's monitor but fail automated contrast check. All category colors appear as similar gray under DevTools "Emulate vision deficiency: Achromatopsia" filter.
+
+### Pitfall 3: i18n Key Regressions
+
+**What goes wrong:** New dashboard text keys added to `en.json` but forgotten in `de.json`. The app silently falls back to English because `fallbackLng: 'en'`.
+**Why it happens:** No build-time key parity check exists. `debug: false` in production hides `missingKey` warnings.
+**How to avoid:** Add both language files in the same commit. Before completing any task, switch locale to German and visually verify no raw key strings appear. Current key counts: `en.json` = 97 keys, `de.json` = 97 keys (parity confirmed).
+**Warning signs:** German UI shows English text or dot-notation strings like `dashboard.carryover`.
+
+### Pitfall 4: Design Inconsistency ("Island Redesign")
+
+**What goes wrong:** Without establishing shared components before page work, each page develops subtly different card styles, heading sizes, and spacing.
+**Why it happens:** Developers implement visual patterns inline in the first page that needs them, then drift in subsequent pages.
+**How to avoid:** This phase exists specifically to prevent this. Build `PageShell`, `StatCard`, and the color token system BEFORE any page redesign begins. All subsequent phases consume these abstractions.
+**Warning signs:** Two pages using different heading sizes or card padding values. Color values appearing as raw oklch literals in component files instead of semantic tokens.
+
+## Code Examples
+
+### Extending index.css Color Tokens
+
+The current `@theme inline` block needs two additions: richer category chroma and semantic status tokens.
+
+```css
+/* src/index.css -- inside existing @theme inline block */
+
+/* Category Colors -- bumped chroma for richer visual style */
+/* IMPORTANT: Verify each pair against --color-card (white) for WCAG 4.5:1 text contrast */
+--color-income: oklch(0.55 0.17 155); /* darkened L from 0.72 for text contrast */
+--color-bill: oklch(0.55 0.17 25); /* darkened L from 0.70 for text contrast */
+--color-variable-expense: oklch(0.58 0.16 50); /* darkened L from 0.72 for text contrast */
+--color-debt: oklch(0.52 0.18 355); /* darkened L from 0.65 for text contrast */
+--color-saving: oklch(0.55 0.16 220); /* darkened L from 0.72 for text contrast */
+--color-investment: oklch(0.55 0.16 285); /* darkened L from 0.70 for text contrast */
+
+/* Semantic Status Tokens -- for budget comparison display */
+--color-over-budget: oklch(0.55 0.20 25); /* red-orange for overspend, verified 4.5:1 on white */
+--color-on-budget: oklch(0.50 0.17 155); /* green for on-track, verified 4.5:1 on white */
+--color-budget-bar-bg: oklch(0.92 0.01 260); /* neutral track for progress bars */
+
+/* Chart fill variants -- lighter versions of category colors for fills */
+/* (original higher-L values are fine for non-text chart fills at 3:1) */
+--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);
+```
+
+**Key insight:** The original category colors (L ~0.65-0.72) are fine for non-text chart fills but too light for text on white backgrounds. The solution is a two-tier system: darker variants (`--color-income`) for text, lighter variants (`--color-income-fill`) for chart fills. This avoids the common trap of choosing colors that look great in charts but fail WCAG when used as text.
+
+**IMPORTANT:** These are recommended starting values. Each pair MUST be verified against `--color-card` (oklch(1 0 0) = white) using OddContrast before committing. Adjust L (lightness) down if any pair fails 4.5:1 for text.
+
+### The chart.tsx Patch
+
+After running `npx shadcn@latest add chart`, locate the `ChartContainer` component in `src/components/ui/chart.tsx` and find the `ResponsiveContainer` element. Apply this change:
+
+```typescript
+// BEFORE (generated by CLI):
+
+ {children}
+
+
+// AFTER (patched for Recharts v3):
+
+ {children}
+
+```
+
+**Verification:** After patching, import `ChartContainer` in any component and render a minimal chart. The browser console should NOT show `"The width(-1) and height(-1) of chart should be greater than 0"`.
+
+### New i18n Keys Required
+
+```json
+// Add to both en.json and de.json dashboard section:
+{
+ "dashboard": {
+ "title": "Dashboard",
+ "totalIncome": "Total Income",
+ "totalExpenses": "Total Expenses",
+ "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..."
+ }
+}
+```
+
+German translations:
+```json
+{
+ "dashboard": {
+ "title": "Dashboard",
+ "totalIncome": "Gesamteinkommen",
+ "totalExpenses": "Gesamtausgaben",
+ "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..."
+ }
+}
+```
+
+## State of the Art
+
+| Old Approach | Current Approach | When Changed | Impact |
+|--------------|------------------|--------------|--------|
+| `tailwind.config.js` JS theme | `@theme inline` in CSS | Tailwind v4 (Jan 2025) | All tokens are native CSS variables; no rebuild for theme changes |
+| `@radix-ui/react-collapsible` | `radix-ui` unified package | June 2025 | shadcn CLI generates `import { Collapsible } from "radix-ui"` not `@radix-ui/react-*` |
+| Recharts v2 `Cell` component | Recharts v3 `shape` prop | Recharts 3.0 (2025) | `Cell` still works but is deprecated; new code should avoid extending Cell usage |
+| Recharts v2 `blendStroke` | `stroke="none"` | Recharts 3.0 | `blendStroke` removed entirely |
+| shadcn chart.tsx for Recharts v2 | Awaiting PR #8486 merge | Pending (March 2026) | Manual `initialDimension` patch required after CLI install |
+| Hardcoded `text-green-600` for status | Semantic CSS variable tokens | This phase | `--color-on-budget` and `--color-over-budget` replace 4 instances of hardcoded green/red |
+
+**Deprecated/outdated in this codebase:**
+- `SummaryCard` in `DashboardPage.tsx` (lines 45-66): Replaced by `StatCard` with variance support
+- Hardcoded `text-green-600 dark:text-green-400` / `text-red-600 dark:text-red-400` patterns: Replace with `text-on-budget` / `text-over-budget` semantic classes
+- Returning `null` during loading states (`DashboardPage.tsx` line 76, 291): Replace with `DashboardSkeleton`
+
+## Existing Code Reference Points
+
+These are the specific files and line numbers that Phase 1 tasks will modify or reference:
+
+| File | Lines | What | Phase 1 Action |
+|------|-------|------|----------------|
+| `src/index.css` | 44-57 | Category + chart color tokens | Extend with richer chroma + semantic status tokens |
+| `src/pages/DashboardPage.tsx` | 45-66 | Existing `SummaryCard` component | Replace with `StatCard` from `components/dashboard/` |
+| `src/pages/DashboardPage.tsx` | 76, 291 | `if (loading) return null` | Replace with skeleton loading |
+| `src/pages/DashboardPage.tsx` | 95-98 | Hardcoded `text-green-600`/`text-red-600` | Replace with semantic `text-on-budget`/`text-over-budget` |
+| `src/pages/DashboardPage.tsx` | 293-298 | Page header `
` | Replace with `PageShell` |
+| `src/pages/BudgetDetailPage.tsx` | 168-173 | Hardcoded green/red in `DifferenceCell` | Replace with semantic tokens (verify only in Phase 1; modify in Phase 4) |
+| `src/lib/palette.ts` | 1-10 | `categoryColors` record | No changes needed -- already maps to CSS variables |
+| `src/lib/format.ts` | 1-12 | `formatCurrency` utility | No changes needed -- used as-is by StatCard |
+| `src/i18n/en.json` | 64-72 | Dashboard translation keys | Extend with new keys |
+| `src/i18n/de.json` | 64-72 | Dashboard translation keys | Extend with matching German keys |
+| `components.json` | 1-21 | shadcn config (new-york style, `@/` aliases) | No changes -- used by `npx shadcn@latest add` |
+
+## Validation Architecture
+
+### Test Framework
+
+| Property | Value |
+|----------|-------|
+| Framework | None -- no test framework installed |
+| Config file | none |
+| Quick run command | `npm run build` (TypeScript + Vite build validates types and imports) |
+| Full suite command | `npm run build && npm run lint` |
+
+### Phase Requirements -> Test Map
+
+| Req ID | Behavior | Test Type | Automated Command | File Exists? |
+|--------|----------|-----------|-------------------|-------------|
+| UI-DASH-01 | StatCard/SummaryStrip render KPI cards with semantic colors | manual | `npm run build` (type-check only) | N/A -- no test infra |
+| UI-DESIGN-01 | Color tokens pass WCAG 4.5:1 contrast | manual | External tool: OddContrast | N/A -- manual verification |
+| UI-RESPONSIVE-01 | Summary card grid responds to viewport width | manual | Browser DevTools responsive mode | N/A -- visual verification |
+
+### Sampling Rate
+
+- **Per task commit:** `npm run build` (catches type errors and import failures)
+- **Per wave merge:** `npm run build && npm run lint`
+- **Phase gate:** Full build green + manual visual verification of all success criteria
+
+### Wave 0 Gaps
+
+- No test framework exists. This is acceptable for a UI-only overhaul where verification is primarily visual.
+- Automated WCAG contrast checking would require adding a tool like `color-contrast-checker` -- defer to project owner's discretion.
+- The `build` command (`tsc -b && vite build`) serves as the primary automated validation: it catches type errors, missing imports, and bundling failures.
+
+## Open Questions
+
+1. **Exact OKLCH lightness values for WCAG compliance**
+ - What we know: Lower lightness (L) = darker color = higher contrast against white. Text needs 4.5:1; chart fills need 3:1.
+ - What's unclear: The exact L threshold depends on chroma and hue. Each of the 8 proposed tokens needs individual verification.
+ - Recommendation: Use OddContrast with OKLCH input. Start with the proposed values (L ~0.50-0.58 for text, L ~0.60-0.70 for fills). Adjust during implementation.
+
+2. **Whether `chart.tsx` patch is still needed at time of execution**
+ - What we know: PR #8486 was open as of research date (2026-03-16). The CLI may merge the fix at any time.
+ - What's unclear: If the PR has merged by execution time, the patch may already be included.
+ - Recommendation: After running `npx shadcn@latest add chart`, check if `initialDimension` is already present. If so, skip the manual patch. If not, apply it.
+
+3. **Chart fill colors vs text colors -- whether two-tier token system is necessary**
+ - What we know: Using the same color for both text and chart fills forces a compromise: either too dark for charts (muddy) or too light for text (fails WCAG).
+ - What's unclear: Whether the visual difference is significant enough to justify 6 extra tokens.
+ - Recommendation: Start with the two-tier system (`--color-income` for text, `--color-income-fill` for fills). If the visual delta is negligible after WCAG verification, collapse to single tokens.
+
+## Sources
+
+### Primary (HIGH confidence)
+- [Tailwind CSS v4 Theme Docs](https://tailwindcss.com/docs/theme) -- `@theme inline`, CSS variable scoping
+- [shadcn/ui Chart Docs](https://ui.shadcn.com/docs/components/radix/chart) -- ChartContainer, ChartConfig, ChartTooltip
+- [Radix UI Collapsible](https://www.radix-ui.com/primitives/docs/components/collapsible) -- `--radix-collapsible-content-height` animation
+- [WCAG 2.1 SC 1.4.3 Contrast Minimum](https://www.w3.org/WAI/WCAG21/Understanding/contrast-minimum.html) -- 4.5:1 for text
+- [WCAG 2.1 SC 1.4.11 Non-text Contrast](https://www.w3.org/WAI/WCAG21/Understanding/non-text-contrast.html) -- 3:1 for UI components
+- Existing codebase: `src/index.css`, `src/pages/DashboardPage.tsx`, `src/lib/palette.ts`, `src/lib/format.ts`, `src/lib/types.ts`, `src/components/ui/skeleton.tsx`, `src/components/ui/card.tsx`, `src/i18n/en.json`, `src/i18n/de.json`, `components.json`
+
+### Secondary (MEDIUM confidence)
+- [shadcn-ui/ui Issue #9892](https://github.com/shadcn-ui/ui/issues/9892) -- Community-verified `initialDimension` fix for Recharts v3
+- [shadcn-ui/ui PR #8486](https://github.com/shadcn-ui/ui/pull/8486) -- Official Recharts v3 chart.tsx upgrade (open as of March 2026)
+- [Recharts V3 with shadcn/ui -- noxify gist](https://gist.github.com/noxify/92bc410cc2d01109f4160002da9a61e5) -- WIP implementation reference
+- [OddContrast](https://www.oddcontrast.com/) -- OKLCH-native WCAG contrast checker
+- [Atmos Contrast Checker](https://atmos.style/contrast-checker) -- OKLCH + APCA contrast tool
+
+### Tertiary (LOW confidence)
+- [Design Tokens That Scale in 2026 (Tailwind v4 + CSS Variables)](https://www.maviklabs.com/blog/design-tokens-tailwind-v4-2026) -- Design token patterns (informational)
+
+## Metadata
+
+**Confidence breakdown:**
+- Standard stack: HIGH -- stack is locked and fully inspected; shadcn CLI commands are documented
+- Architecture: HIGH -- component boundaries derived from existing codebase inspection; patterns follow official shadcn/Radix docs
+- Pitfalls: HIGH -- chart.tsx patch verified against issue #9892 and gist; WCAG requirements from official W3C specs; i18n issue confirmed by codebase inspection (fallbackLng: 'en' hides missing keys)
+- Color tokens: MEDIUM -- proposed OKLCH values need runtime WCAG verification; starting values are educated estimates based on lightness/contrast relationship
+
+**Research date:** 2026-03-16
+**Valid until:** 2026-04-16 (30 days -- stable domain; only chart.tsx patch status may change if PR #8486 merges)