diff --git a/.planning/phases/01-design-foundation-and-primitives/01-RESEARCH.md b/.planning/phases/01-design-foundation-and-primitives/01-RESEARCH.md new file mode 100644 index 0000000..2ce71ef --- /dev/null +++ b/.planning/phases/01-design-foundation-and-primitives/01-RESEARCH.md @@ -0,0 +1,548 @@ +# Phase 1: Design Foundation and Primitives - Research + +**Researched:** 2026-03-16 +**Domain:** Design system tokens (OKLCH/CSS variables), shadcn/ui primitives, shared React components +**Confidence:** HIGH + +## Summary + +Phase 1 establishes the design system building blocks that every subsequent phase consumes. The work breaks into four domains: (1) installing shadcn/ui primitives (`chart` and `collapsible`) with the known Recharts v3 compatibility patch, (2) extending the existing OKLCH color token system in `index.css` with richer category chroma and semantic status tokens, (3) building two shared components (`PageShell` for consistent page headers and `StatCard`/`SummaryStrip` for KPI cards), and (4) creating skeleton loading components that mirror the final dashboard layout. + +The existing codebase already has a well-structured `@theme inline` block in `index.css` with six category colors and five chart colors, a `palette.ts` mapping those CSS variables to a TypeScript record, and a `formatCurrency` utility. The current `DashboardPage.tsx` contains a simple `SummaryCard` component and an unmemoized `DashboardContent` function that this phase will partially replace. The shadcn/ui `skeleton.tsx` primitive already exists in `components/ui/`. + +The highest-risk item is the `chart.tsx` Recharts v3 patch. The generated `chart.tsx` from `npx shadcn@latest add chart` requires adding `initialDimension={{ width: 320, height: 200 }}` to the `ResponsiveContainer` inside `ChartContainer`. Without this, all charts will produce `width(-1) and height(-1)` console warnings and may render at zero dimensions. The patch is documented in shadcn-ui/ui issue #9892 and is a one-line fix. + +**Primary recommendation:** Install primitives first, patch chart.tsx immediately, then extend tokens, then build shared components, then skeletons. This order ensures each layer is available before the next layer depends on it. + + +## Phase Requirements + +| ID | Description | Research Support | +|----|-------------|-----------------| +| UI-DASH-01 | Redesign dashboard with hybrid layout -- summary cards, charts, and collapsible category sections | This phase delivers the summary cards layer (StatCard/SummaryStrip) and installs the chart and collapsible primitives that Phase 2 and 3 will consume. The existing `SummaryCard` in DashboardPage.tsx is replaced with a richer `StatCard` component with semantic color coding and variance badges. | +| UI-DESIGN-01 | Redesign all pages with rich, colorful visual style -- consistent design language | This phase delivers the design foundation: extended OKLCH color tokens with richer chroma (0.18+ vs current 0.14), semantic status tokens (`--color-over-budget`, `--color-on-budget`), and `PageShell` -- the shared component that enforces consistent page headers across all 9 pages. Without this phase, design drift (Pitfall 6) is guaranteed. | +| UI-RESPONSIVE-01 | Desktop-first responsive layout across all pages | This phase sets the responsive grid patterns for summary cards (`grid-cols-1 sm:grid-cols-2 lg:grid-cols-3`) and establishes `PageShell` with responsive padding and header layout. All subsequent phases inherit these breakpoints. | + + +## Standard Stack + +### Core (Already Installed -- No New Packages) + +| Library | Version | Purpose | Status | +|---------|---------|---------|--------| +| React | 19.2.4 | UI framework | Locked | +| Tailwind CSS | 4.2.1 | Styling via `@theme inline` tokens | Locked | +| Recharts | 3.8.0 | Charts (consumed by Phase 2, but `chart.tsx` wrapper installed here) | Locked | +| radix-ui | 1.4.3 | Primitives (Collapsible, Accordion) | Locked | +| Lucide React | 0.577.0 | Icons (TrendingUp, TrendingDown, ChevronDown) | Locked | +| shadcn/ui | new-york style | UI component library (Card, Badge, Skeleton, etc.) | Locked | + +### shadcn/ui Primitives to Add (Phase 1 Deliverables) + +| Component | Install Command | Purpose | Post-Install Action | +|-----------|----------------|---------|---------------------| +| `chart` | `npx shadcn@latest add chart` | `ChartContainer`, `ChartTooltip`, `ChartTooltipContent` wrappers | **CRITICAL:** Patch `chart.tsx` -- add `initialDimension={{ width: 320, height: 200 }}` to `ResponsiveContainer` | +| `collapsible` | `npx shadcn@latest add collapsible` | Radix `Collapsible` primitive for Phase 3 category sections | None -- install and verify import works | + +### What NOT to Add + +| Avoid | Why | +|-------|-----| +| `accordion` | Research initially suggested it, but `Collapsible` gives independent per-section state without fighting Accordion's root-state coordination. Use individual `Collapsible` per `CategorySection`. | +| Framer Motion | CSS transitions via `transition-all duration-200` cover all needed animations. No bundle weight added. | +| Any new npm package | Stack is locked. All additions are shadcn CLI-generated component files, not npm dependencies. | + +## Architecture Patterns + +### Recommended Project Structure (Phase 1 Additions) + +``` +src/ + components/ + ui/ + chart.tsx # ADD via shadcn CLI + apply initialDimension patch + collapsible.tsx # ADD via shadcn CLI + skeleton.tsx # EXISTS -- already installed + card.tsx # EXISTS -- used by StatCard + badge.tsx # EXISTS -- used for variance badges + dashboard/ # ADD -- dashboard-specific view components + StatCard.tsx # KPI card with semantic color, value, label, variance badge + SummaryStrip.tsx # Row of 3 StatCards (income, expenses, balance) + DashboardSkeleton.tsx # Skeleton loading for cards + chart placeholders + shared/ # ADD -- cross-page reusable components + PageShell.tsx # Consistent page header with title, description, CTA slot + index.css # MODIFY -- extend @theme inline with richer tokens + i18n/ + en.json # MODIFY -- add new dashboard keys + de.json # MODIFY -- add new dashboard keys (same commit) +``` + +### Pattern 1: PageShell -- Consistent Page Header + +**What:** A wrapper component that enforces consistent heading size, spacing, optional description, and CTA slot across all pages. +**When to use:** Every page in the app wraps its top section in `PageShell`. + +```typescript +// src/components/shared/PageShell.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:** +- `text-2xl font-semibold tracking-tight` matches the existing `DashboardPage` heading style +- `action` is a `ReactNode` slot, not a button-specific prop -- allows any CTA element +- No `padding` baked in -- the `
` in `AppLayout.tsx` already applies `p-6` +- The existing `DashboardPage` header (`
`) 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)