diff --git a/.planning/research/ARCHITECTURE.md b/.planning/research/ARCHITECTURE.md
new file mode 100644
index 0000000..6c8dbf4
--- /dev/null
+++ b/.planning/research/ARCHITECTURE.md
@@ -0,0 +1,410 @@
+# Architecture Patterns: Design System
+
+**Domain:** shadcn/ui + Tailwind CSS 4 design system for personal finance dashboard
+**Researched:** 2026-03-11
+**Confidence:** HIGH (based on direct codebase inspection + framework documentation patterns)
+
+---
+
+## Recommended Architecture
+
+The goal is a pastel design system layered on top of shadcn/ui without replacing it. The architecture has three tiers:
+
+1. **Token layer** — CSS custom properties in `index.css` (already exists, needs pastel values)
+2. **Variant layer** — CVA-based component variants in `components/ui/` (shadcn files, lightly patched)
+3. **Composition layer** — Feature components in `components/` that assemble ui primitives with domain semantics
+
+```
+frontend/src/
+ index.css ← Token layer: ALL CSS variables live here
+ lib/
+ utils.ts ← cn() helper (already exists)
+ components/
+ ui/ ← Variant layer: shadcn primitives (owned, patchable)
+ button.tsx
+ card.tsx
+ badge.tsx
+ ...
+ category-badge.tsx ← Composition layer: domain-specific wrapper
+ stat-card.tsx ← Composition layer: reusable financial card pattern
+ progress-bar.tsx ← Composition layer: budget vs actual bar
+ page-header.tsx ← Composition layer: consistent page headers
+ pages/ ← Page layer: route views that compose components
+```
+
+---
+
+## Component Boundaries
+
+| Component | Responsibility | Communicates With |
+|-----------|---------------|-------------------|
+| `index.css` | All CSS variables and `@theme inline` mappings | Tailwind engine only |
+| `components/ui/*` | Headless + styled primitives (shadcn-owned) | Tailwind classes, CVA variants |
+| `components/*.tsx` | Domain-aware compositions (app-owned) | ui primitives, hooks, i18n |
+| `pages/*.tsx` | Route-level views | components, hooks, router |
+| `hooks/*.ts` | Data fetching + state | API client only |
+
+The key boundary: `components/ui/` components must not import domain types from `lib/api.ts`. Only `components/*.tsx` and `pages/*.tsx` know about API shapes.
+
+---
+
+## Data Flow
+
+```
+CSS Variables (index.css)
+ ↓ consumed by
+Tailwind @theme inline → utility classes (bg-primary, text-muted-foreground, etc.)
+ ↓ applied in
+shadcn ui primitives (button, card, badge, input)
+ ↓ assembled into
+Domain components (stat-card, category-badge, progress-bar)
+ ↓ composed into
+Pages (DashboardPage, LoginPage, CategoriesPage, SettingsPage)
+ ↑ data from
+Hooks (useAuth, useBudgets) → API client (lib/api.ts) → Go REST API
+```
+
+Styling flows **downward** (tokens → primitives → components → pages). Data flows **upward** (API → hooks → pages → components via props).
+
+---
+
+## Patterns to Follow
+
+### Pattern 1: Token-First Color System
+
+**What:** Define all palette values as CSS custom properties in `:root`, then map them to Tailwind via `@theme inline`. Never write raw color values (`oklch(0.88 0.06 310)`) in component files.
+
+**When:** Any time a new color is needed for the pastel palette.
+
+**How it works in this codebase:**
+
+The project uses Tailwind 4 which reads tokens exclusively from `@theme inline {}` in index.css. The two-step pattern already present in the codebase is correct — add raw values to `:root {}`, then expose them as Tailwind utilities in `@theme inline {}`.
+
+```css
+/* Step 1: Define semantic tokens in :root */
+:root {
+ /* Pastel palette — raw values */
+ --pastel-pink: oklch(0.92 0.04 340);
+ --pastel-blue: oklch(0.92 0.04 220);
+ --pastel-green: oklch(0.92 0.04 145);
+ --pastel-amber: oklch(0.92 0.06 80);
+ --pastel-violet: oklch(0.92 0.04 290);
+ --pastel-sky: oklch(0.94 0.04 215);
+
+ /* Semantic role tokens — reference palette */
+ --color-income: var(--pastel-green);
+ --color-bill: var(--pastel-blue);
+ --color-expense: var(--pastel-amber);
+ --color-debt: var(--pastel-pink);
+ --color-saving: var(--pastel-violet);
+ --color-investment: var(--pastel-sky);
+
+ /* Override shadcn semantic tokens with pastel values */
+ --primary: oklch(0.55 0.14 255); /* soft indigo */
+ --background: oklch(0.985 0.005 240); /* barely-blue white */
+ --card: oklch(1 0 0);
+ --muted: oklch(0.96 0.01 240);
+}
+
+/* Step 2: Expose as Tailwind utilities inside @theme inline */
+@theme inline {
+ /* Existing shadcn bridge (keep as-is) */
+ --color-primary: var(--primary);
+ --color-background: var(--background);
+ /* ... */
+
+ /* New pastel category utilities */
+ --color-income: var(--color-income);
+ --color-bill: var(--color-bill);
+ --color-expense: var(--color-expense);
+ --color-debt: var(--color-debt);
+ --color-saving: var(--color-saving);
+ --color-investment: var(--color-investment);
+}
+```
+
+Components then use `bg-income`, `text-bill`, `bg-saving/30` etc. as Tailwind classes.
+
+### Pattern 2: Category Color Mapping via CVA
+
+**What:** A single `categoryVariants` CVA definition maps category types to their pastel color classes. Any component that needs category coloring imports this one function.
+
+**When:** CategoryBadge, table rows in FinancialOverview, chart color arrays, category icons.
+
+```typescript
+// components/category-badge.tsx
+import { cva } from "class-variance-authority"
+
+export const categoryVariants = cva(
+ "inline-flex items-center rounded-full px-2.5 py-0.5 text-xs font-medium",
+ {
+ variants: {
+ type: {
+ income: "bg-income/20 text-income-foreground",
+ bill: "bg-bill/20 text-bill-foreground",
+ variable_expense:"bg-expense/20 text-expense-foreground",
+ debt: "bg-debt/20 text-debt-foreground",
+ saving: "bg-saving/20 text-saving-foreground",
+ investment: "bg-investment/20 text-investment-foreground",
+ },
+ },
+ }
+)
+```
+
+This is the single source of truth for category-to-color mapping. Chart colors derive from the same CSS variables (not hardcoded hex).
+
+### Pattern 3: Wrapper Components Over shadcn Modification
+
+**What:** When domain semantics need to be expressed (e.g., a "stat card" showing budget vs. actual), create a wrapper component in `components/` that uses shadcn Card internally. Do not modify `components/ui/card.tsx` for domain logic.
+
+**When:** Any component that appears more than twice with the same structure across pages.
+
+```typescript
+// components/stat-card.tsx — app-owned, domain-aware
+import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
+import { cn } from "@/lib/utils"
+
+interface StatCardProps {
+ title: string
+ value: string
+ subtext?: string
+ accent?: "income" | "bill" | "expense" | "debt" | "saving" | "investment"
+}
+
+export function StatCard({ title, value, subtext, accent }: StatCardProps) {
+ return (
+
+ {title}
+
+
{value}
+ {subtext &&
{subtext}
}
+
+
+ )
+}
+```
+
+### Pattern 4: Extend shadcn Primitives via className Override Only
+
+**What:** When a shadcn component needs a slight visual adjustment, pass a `className` prop using `cn()` to override. Do not fork the component unless adding a new CVA variant that belongs at the primitive level.
+
+**When:** One-off adjustments in page/feature components.
+
+**When to actually patch `components/ui/`:** Adding a CVA variant used in 3+ places (e.g., adding a `pastel` button variant). Keep patches minimal and document them.
+
+```typescript
+// Good — className override in consumer
+
+
+// Good — new CVA variant if used everywhere
+// In button.tsx, add to variants.variant:
+pastel: "bg-primary/15 text-primary hover:bg-primary/25 border-primary/20",
+```
+
+### Pattern 5: Recharts Color Consistency
+
+**What:** Recharts color arrays must reference the same CSS variables used in Tailwind classes. Use `getComputedStyle` to read the variable at render time, not hardcoded hex values.
+
+**When:** Any chart component (donut, bar, pie in ExpenseBreakdown, FinancialOverview).
+
+```typescript
+// In chart component or a lib/colors.ts utility
+function getCategoryColor(type: CategoryType): string {
+ return getComputedStyle(document.documentElement)
+ .getPropertyValue(`--color-${type}`)
+ .trim()
+}
+```
+
+---
+
+## Anti-Patterns to Avoid
+
+### Anti-Pattern 1: Inline Color Values in Components
+
+**What:** Writing `bg-sky-50`, `bg-emerald-50`, `bg-amber-50` directly in component files (already present in `FinancialOverview.tsx`).
+
+**Why bad:** The color assignment is scattered across every component. Changing a category color requires hunting every file. The existing code in `FinancialOverview.tsx` rows array hardcodes `color: 'bg-sky-50'` — this must be replaced by the category variant system.
+
+**Instead:** All category-to-color mappings live in `categoryVariants` (CVA, single file). Components only pass the category type.
+
+### Anti-Pattern 2: Separate CSS Files per Component
+
+**What:** Creating `StatCard.css`, `Dashboard.css` etc.
+
+**Why bad:** The project uses Tailwind 4's `@theme inline` — all custom styles belong in `index.css` or as Tailwind utilities. Split CSS files fragment the token system.
+
+**Instead:** All tokens in `index.css`. All component-specific styles as Tailwind classes or CVA variants.
+
+### Anti-Pattern 3: Dark Mode Parallel Pastel Palette
+
+**What:** Trying to create a full pastel dark mode in the same milestone.
+
+**Why bad:** High complexity for low value (budgeting is primarily a desktop daytime activity). The PROJECT.md explicitly defers custom themes.
+
+**Instead:** Keep `.dark {}` block in index.css as-is (it's already defined). Focus dark mode on the existing neutral dark values. Pastel = light mode only for this milestone.
+
+### Anti-Pattern 4: Tailwind `tailwind.config.js` for Custom Tokens
+
+**What:** Creating a `tailwind.config.js` or `tailwind.config.ts` to add custom colors.
+
+**Why bad:** This project uses Tailwind CSS 4 (`@import "tailwindcss"` in index.css, `@tailwindcss/vite` plugin). Tailwind 4 configures everything through CSS — `tailwind.config.js` is a Tailwind 3 pattern. Using both causes conflicts and confusion.
+
+**Instead:** All custom tokens go in `@theme inline {}` inside `index.css`.
+
+---
+
+## CSS Variable Organization
+
+The existing `index.css` should be organized into clearly labeled sections:
+
+```css
+/* 1. FRAMEWORK IMPORTS */
+@import "tailwindcss";
+@import "tw-animate-css";
+@import "shadcn/tailwind.css";
+@import "@fontsource-variable/geist";
+
+/* 2. CUSTOM DARK VARIANT */
+@custom-variant dark (&:is(.dark *));
+
+/* 3. LIGHT MODE TOKENS */
+:root {
+ /* === PASTEL PALETTE (raw values) === */
+ --pastel-pink: ...;
+ --pastel-blue: ...;
+ /* ... */
+
+ /* === CATEGORY SEMANTIC TOKENS === */
+ --color-income: var(--pastel-green);
+ /* ... */
+
+ /* === SHADCN SEMANTIC TOKENS (override defaults) === */
+ --background: ...;
+ --primary: ...;
+ /* ... all existing shadcn vars ... */
+
+ /* === CHART TOKENS === */
+ --chart-1: ...;
+ /* ... */
+}
+
+/* 4. DARK MODE OVERRIDES */
+.dark {
+ /* shadcn dark vars only — no dark pastel */
+}
+
+/* 5. TAILWIND BRIDGE (@theme inline) */
+@theme inline {
+ /* font */
+ --font-sans: 'Geist Variable', sans-serif;
+
+ /* shadcn token bridge (existing) */
+ --color-background: var(--background);
+ --color-primary: var(--primary);
+ /* ... */
+
+ /* category token bridge (new) */
+ --color-income: var(--color-income);
+ --color-bill: var(--color-bill);
+ /* ... */
+
+ /* radius scale (existing) */
+ --radius-sm: calc(var(--radius) * 0.6);
+ /* ... */
+}
+
+/* 6. BASE STYLES */
+@layer base {
+ * { @apply border-border outline-ring/50; }
+ body { @apply bg-background text-foreground; }
+ html { @apply font-sans; }
+}
+```
+
+---
+
+## File Structure: Design System
+
+```
+frontend/src/
+ index.css ← SINGLE SOURCE: all tokens, @theme inline, base
+ lib/
+ utils.ts ← cn() (keep as-is)
+ colors.ts ← NEW: getCategoryColor() for Recharts, CATEGORY_TYPES map
+ components/
+ ui/ ← shadcn primitives (patchable with CVA variants)
+ button.tsx ← Add: pastel variant
+ badge.tsx ← Add: category type variants via CVA
+ card.tsx ← Keep as-is (className override is sufficient)
+ input.tsx ← May need focus ring color patch
+ ...
+ category-badge.tsx ← NEW: domain badge using badge.tsx + categoryVariants
+ stat-card.tsx ← NEW: financial metric card
+ progress-bar.tsx ← NEW: budget vs actual with color coding
+ page-header.tsx ← NEW: consistent page header with title + actions slot
+ empty-state.tsx ← NEW: consistent empty state for lists
+```
+
+---
+
+## Scalability Considerations
+
+| Concern | Current scope | Future (theming milestone) |
+|---------|--------------|---------------------------|
+| Color tokens | Pastel light mode only | Add `data-theme="ocean"` etc. on ``, swap `:root` vars |
+| Dark mode | Neutral dark (existing) | Pastel dark values in `.dark {}` |
+| Component variants | Per-category CVA | Per-theme variant maps |
+| Chart colors | CSS variable lookup | Same — already theme-aware |
+
+The CSS variable architecture supports future theming without structural change. When custom themes are added, only `:root` values change; components need zero modification.
+
+---
+
+## Build Order: Foundation to Pages
+
+This is the correct sequence because each layer depends on the previous:
+
+**Phase A: Token foundation**
+1. Define pastel palette in `index.css` `:root` (raw oklch values)
+2. Define category semantic tokens in `index.css`
+3. Add category tokens to `@theme inline` bridge
+4. Define pastel overrides for shadcn semantic tokens (background, primary, etc.)
+
+**Phase B: Primitive polish**
+5. Patch `button.tsx` — add `pastel` variant, adjust default hover states
+6. Patch `badge.tsx` — add category type variants
+7. Patch `input.tsx` — ensure focus ring uses `--ring` pastel value
+8. Create `lib/colors.ts` — getCategoryColor() and category type constants
+
+**Phase C: Domain components**
+9. Create `category-badge.tsx` — wraps badge with category semantics
+10. Create `stat-card.tsx` — financial metric display card
+11. Create `progress-bar.tsx` — budget vs actual visual
+12. Create `page-header.tsx` — consistent header slot
+13. Create `empty-state.tsx` — consistent empty list state
+
+**Phase D: Page-by-page polish**
+14. Login + Register pages — full branded treatment (no generic shadcn defaults)
+15. Dashboard — replace hardcoded color strings with category variants, add stat cards
+16. Categories page — category badge integration, polished CRUD UI
+17. Settings page — form layout, preference toggles
+
+**Phase E: Charts**
+18. ExpenseBreakdown — donut chart with CSS variable colors
+19. FinancialOverview chart — bar chart with category palette
+20. AvailableBalance — progress indicator
+
+Build order rationale: Pages cannot be polished until domain components exist. Domain components cannot use consistent tokens until the token layer is established. Charts come last because they depend on both the token system and the data components being stable.
+
+---
+
+## Sources
+
+- Direct inspection of `frontend/src/index.css` — confirms Tailwind 4 (`@import "tailwindcss"`, `@theme inline`) and oklch color space already in use
+- Direct inspection of `frontend/components.json` — confirms shadcn/ui radix-nova style, `cssVariables: true`, Tailwind 4 CSS-only config (no `tailwind.config.ts`)
+- Direct inspection of `frontend/src/components/ui/button.tsx` — confirms CVA pattern with `class-variance-authority` already in use
+- Direct inspection of `frontend/src/components/FinancialOverview.tsx` — identifies hardcoded color anti-pattern (`bg-sky-50`, `bg-emerald-50`) that needs migration
+- `.planning/codebase/STACK.md` — confirms Tailwind CSS 4.2.1, shadcn/ui 4.0.0, CVA 0.7.1
+- `.planning/PROJECT.md` — confirms pastel spreadsheet aesthetic goal, CSS variable customization approach, desktop-first target
diff --git a/.planning/research/FEATURES.md b/.planning/research/FEATURES.md
new file mode 100644
index 0000000..f39db41
--- /dev/null
+++ b/.planning/research/FEATURES.md
@@ -0,0 +1,156 @@
+# Feature Landscape — UI/UX Polish
+
+**Domain:** Personal finance dashboard (pastel spreadsheet aesthetic)
+**Researched:** 2026-03-11
+**Milestone scope:** Existing backend is complete. This pass is purely about making what exists look and feel premium.
+
+---
+
+## What "Premium" Means for This App
+
+The stated vision is "opening the app should feel like opening a beautifully designed personal spreadsheet." That is a specific aesthetic contract: data-dense but not cluttered, color-coded but soft, functional but delightful. A premium personal finance tool is not a banking dashboard (dark, heavy, corporate) — it is closer to Notion or Linear: calm, spacious, consistent, with color that communicates meaning rather than decoration.
+
+The gap today: The CSS variables are pure neutral (zero chroma in all OKLCH values), the card headers have correct pastel gradient classes but no actual pastel primary/accent colors, the login screen is a plain white card on a plain white background, and the sidebar has no brand identity. Everything works but nothing _looks_ intentional.
+
+---
+
+## Table Stakes
+
+Features users expect. Missing = product feels unfinished.
+
+| Feature | Why Expected | Current State | Complexity | Notes |
+|---------|--------------|---------------|------------|-------|
+| **Pastel color system in CSS variables** | The entire visual identity depends on it — currently all chroma: 0 (neutral grey) | Missing — `--primary`, `--accent`, `--chart-*` use zero-chroma values | Low | Single change in `index.css` unlocks color across every shadcn component. The card header gradient classes are already correct; they just have no color to work with. |
+| **Login/Register: branded background** | Auth screen is the first impression. White card on white screen communicates nothing | Just `bg-background` (white) | Low | Add a soft pastel gradient or subtle pattern to the `min-h-screen` wrapper |
+| **Login/Register: app logo or wordmark** | Users expect a visual identity at the entry point | `CardDescription` shows `app.title` as plain text | Low | Even a styled typographic treatment works without designing an icon |
+| **Sidebar: visual brand identity** | Sidebar is always visible — it anchors the whole app's look | Plain white sidebar, `h2` text for title, no color differentiation | Low–Med | Pastel sidebar background, heavier app name treatment |
+| **Active nav item: clear visual indicator** | Users need to know where they are. shadcn `isActive` prop exists but needs styled colors | `isActive` is wired, unstyled | Low | CSS variable for sidebar-primary needs a real color |
+| **Card hover states on inline-editable rows** | The edit trigger is `hover:bg-muted` — with no muted color it's invisible | No visible hover feedback | Low | Requires a non-neutral `--muted` value |
+| **Loading state: spinner/pulse on form submission** | Buttons say "disabled" during async calls but give no visual motion feedback | `disabled` only | Low | Add Tailwind `animate-pulse` or a lucide `Loader2` spinner inside submit buttons |
+| **Page-level loading skeleton for dashboard** | Dashboard has `` calls but skeleton is unstyled (neutral grey) | Skeleton exists, no pastel color | Low | Style skeletons to match section colors |
+| **Empty state: first-time experience** | The `t('dashboard.noBudgets')` card is just grey text. First-time users need direction | Bare `text-muted-foreground` text | Low–Med | Illustrative empty state with a CTA to create the first budget |
+| **Category empty state on CategoriesPage** | If no categories exist (possible state), nothing renders — `grouped.filter` could produce empty array | Returns silently empty | Low | Add empty state card with create CTA |
+| **Error feedback on form failures** | Auth errors show raw error strings; no visual differentiation from normal text | `text-destructive` paragraph, but destructive color needs styling | Low | Pair with error icon and styled alert block |
+| **Consistent section headers** | Every dashboard card uses a slightly different gradient (sky-to-indigo, blue-to-indigo, pink-to-rose, etc.) — good start but the overall palette looks fragmented | Each card picks ad-hoc gradient colors | Low | Align gradients to a single palette family derived from the pastel color system |
+| **Typography hierarchy** | Dashboard page has no visual hierarchy — all cards look equal weight. The `FinancialOverview` and `AvailableBalance` should feel like the hero items | All cards same shadow/border/weight | Low–Med | Use subtle elevation (shadow-sm vs shadow) or card size differences to establish hierarchy |
+| **Positive/negative amount coloring** | `VariableExpenses` colors negative remaining red via `text-destructive` — good. `FinancialOverview` and `BillsTracker` do not | Inconsistent | Low | Extend the pattern to all currency display: negative = destructive, over-budget = warning amber |
+| **Responsive sidebar: collapsible on smaller screens** | SidebarProvider supports it, but no toggle button is present in the layout | Not surfaced | Low | Add hamburger/collapse trigger to SidebarInset header area |
+
+---
+
+## Differentiators
+
+Features that are not expected but elevate the experience from "usable" to "delightful."
+
+| Feature | Value Proposition | Current State | Complexity | Notes |
+|---------|-------------------|---------------|------------|-------|
+| **Donut chart center label shows month + year** | The `AvailableBalance` donut already has an amount in the center. Adding context (month name) makes it more meaningful at a glance | Only the amount | Low | Render `budget.name` or derived month/year below the amount |
+| **Budget health indicator on header** | A small color-coded badge next to the budget selector (green = on track, amber = tight, red = over) derived from `totals.available` vs total income | Not present | Low–Med | Purely computed from existing data, no backend change |
+| **Inline edit: visual affordance (pencil icon on hover)** | Current inline edit is discoverable only by hovering and noticing the background change. A pencil icon makes it explicit | Background hint only | Low | Add a `lucide-react` `Pencil` icon that fades in on row hover |
+| **Save confirmation: row flash on successful inline edit** | After blur/Enter saves a value, nothing acknowledges the save. A brief green background flash (100ms) confirms the action succeeded | No feedback | Low–Med | CSS `transition` + toggled class; requires tracking save success in row state |
+| **Category creation inside budget item add flow** | Users currently must leave the dashboard, go to Categories, create a category, then return. Linking category management directly from a "add item" action would reduce friction | No cross-page flow | High | Requires dialog-within-dialog or slide-over; warrants its own milestone item |
+| **Month navigator: prev/next arrows beside budget selector** | Instead of only a dropdown, allow quick sequential navigation through budgets. Most months are adjacent | Dropdown only | Med | Would need sorting budgets by date and prev/next pointer logic |
+| **Chart tooltips: formatted with currency** | Recharts `` renders raw numbers. Formatting with the budget's currency makes tooltips professional | Raw number in tooltip | Low | Custom `Tooltip` formatter using the existing `formatCurrency` util |
+| **Progress bars in BillsTracker for actual vs budget** | Seeing 85% of a bill paid vs just raw numbers adds spatial meaning | Tables only | Med | Bar per row using `actual/budgeted` ratio with color based on threshold |
+| **Animated number transitions** | When inline edits are saved and totals recompute, numbers jump. A short count animation (200ms) makes the update feel responsive | Hard jump | Med | Use a lightweight counter animation hook; only applies to summary numbers in FinancialOverview and AvailableBalance |
+| **Savings/Investments: visual progress toward goal** | Savings and investment items have both budgeted and actual amounts. A thin progress arc per item communicates progress toward monthly target | Table rows only | Med | Small inline arc per row using SVG or Recharts |
+
+---
+
+## Anti-Features
+
+Things to explicitly NOT build during this polish pass.
+
+| Anti-Feature | Why Avoid | What to Do Instead |
+|--------------|-----------|-------------------|
+| **Dark mode toggle** | CSS variables for dark are already in `index.css` but the pastel system must be defined first. Dark pastel is a distinct design problem. Adding a toggle now couples two unfinished systems. | Get the light mode color system right first; dark mode in a future milestone |
+| **Custom color picker / theme selector** | PROJECT.md calls this out explicitly as out of scope. Complex state with no current value. | The color tokens are centralized — theme variants can be added later cleanly |
+| **Animated page transitions** | Route-level enter/exit animations (Framer Motion) add JS weight and complexity for marginal benefit in a data-first app | Keep transitions limited to within-page state changes (inline edits, loading states) |
+| **Toast notifications for every action** | Inline save feedback (row flash) is more contextual. Global toasts for every data save are noisy in a dashboard that supports rapid sequential edits | Use toast only for errors and destructive actions (delete), not saves |
+| **Drag-to-reorder categories on the dashboard** | `sort_order` exists on categories but reordering on the dashboard itself is a UX scope expansion. The Categories page is the right place for sort management | Accept keyboard-ordered sort for this milestone |
+| **Charts outside of existing sections** | Do not add new chart types (e.g., multi-month trend line, sparklines per category) in this pass. The goal is polishing existing charts, not adding new data views | Polish the existing Pie, Donut, and BarChart; trend analysis is a future milestone |
+| **Onboarding wizard / guided tour** | First-run experience improvements are valuable but a multi-step wizard is high complexity for a single-user self-hosted app | An improved empty state with a clear CTA is sufficient |
+| **Keyboard shortcuts / command palette** | Power-user feature that belongs after the visual foundation is established | Out of scope for this milestone |
+
+---
+
+## Feature Dependencies
+
+```
+Pastel CSS variable system
+ → active nav item colors (sidebar-primary needs chroma)
+ → card hover states (muted needs chroma)
+ → skeleton background color
+ → button loading spinners (match primary color)
+ → progress bars in BillsTracker (use existing chart color tokens)
+ → budget health badge colors
+
+Login branded background
+ → requires background to have a color, which needs the pastel system
+
+Inline edit save confirmation (row flash)
+ → requires inline edit mechanism (already exists)
+ → requires knowing save succeeded (currently onSave returns a Promise — can use resolution)
+
+Chart tooltip currency formatting
+ → requires access to budget.currency (already in component scope)
+ → no other dependencies
+
+Month navigator (prev/next)
+ → requires budget list sorted by date
+ → requires selectBudget to be called (already available in useBudgets hook)
+```
+
+---
+
+## MVP Recommendation for This Polish Milestone
+
+These should be done in order because earlier items unlock later items visually.
+
+**First (foundation — everything else depends on it):**
+1. Redefine `--primary`, `--accent`, `--muted`, `--sidebar`, `--chart-1` through `--chart-5` in `index.css` with actual pastel OKLCH values. This single change transforms the entire app.
+2. Apply branded login/register background — a soft pastel gradient wrapping the card.
+
+**Second (structural polish — visible on every page load):**
+3. Sidebar brand treatment — pastel sidebar background, heavier app name, colored active nav.
+4. Typography hierarchy on dashboard — make FinancialOverview + AvailableBalance visually the hero row.
+5. Consistent card section header palette — unify the gradient choices across all dashboard cards.
+
+**Third (interaction quality):**
+6. Loading spinners inside submit buttons (login, save, budget create).
+7. Inline edit affordance — pencil icon on row hover.
+8. Save confirmation flash on inline edit success.
+9. Positive/negative amount coloring applied consistently across all tables.
+
+**Fourth (completeness — guards against "unfinished" feeling):**
+10. Empty state for first-time dashboard (no budgets yet).
+11. Empty state for CategoriesPage.
+12. Chart tooltips formatted with currency.
+13. Collapsible sidebar toggle for smaller screens.
+
+**Defer:**
+- Progress bars in BillsTracker: useful but higher complexity; tackle if time allows.
+- Animated number transitions: medium complexity, medium payoff.
+- Category creation inline from dashboard: high complexity, belongs in a future milestone.
+- Month navigator: medium complexity; polish-pass bonus if foundation is solid.
+
+---
+
+## Confidence Assessment
+
+| Area | Confidence | Notes |
+|------|------------|-------|
+| Gap analysis (what's missing) | HIGH | Based on direct code review — all findings are grounded in the actual source |
+| Table stakes categorization | HIGH | Standard UI/UX craft, no library-specific claims |
+| Differentiators | MEDIUM | Based on domain experience with personal finance tools; no external sources were available during this research session |
+| Anti-features | HIGH | Grounded in PROJECT.md constraints and direct complexity analysis |
+
+**Note on research method:** WebSearch and Context7 were not available in this session. All findings are derived from direct codebase analysis (all frontend source files read) and domain knowledge of finance dashboard UX patterns. The gap analysis is HIGH confidence because it is based on code inspection. The "premium differentiators" section carries MEDIUM confidence because it reflects design judgment rather than verified external benchmarks. Recommend treating differentiators as starting proposals for the roadmap rather than firm requirements.
+
+---
+
+## Sources
+
+- Direct review: all files under `/frontend/src/` (pages, components, hooks, App.tsx, index.css)
+- Project constraints: `.planning/PROJECT.md`
+- Design intent: `CLAUDE.md` architecture notes
diff --git a/.planning/research/PITFALLS.md b/.planning/research/PITFALLS.md
new file mode 100644
index 0000000..a13cbb6
--- /dev/null
+++ b/.planning/research/PITFALLS.md
@@ -0,0 +1,280 @@
+# Domain Pitfalls: UI Polish for SimpleFinanceDash
+
+**Domain:** React + shadcn/ui frontend overhaul — pastel design system on an existing functional app
+**Researched:** 2026-03-11
+**Confidence:** HIGH (grounded in direct codebase inspection)
+
+---
+
+## Critical Pitfalls
+
+Mistakes that cause rewrites, visual regressions across the whole app, or consistency breakdown.
+
+---
+
+### Pitfall 1: Hardcoded Color Values Bypassing the Token System
+
+**What goes wrong:** The codebase already mixes two color approaches. Some components use shadcn CSS variable tokens (`bg-muted`, `text-destructive`, `hover:bg-muted`) while others hardcode Tailwind palette values (`bg-emerald-50`, `bg-violet-100 text-violet-800`, `fill="#fcd34d"`, `#93c5fd`, `#f9a8d4`). If the pastel palette is defined purely as CSS variable tokens in `index.css` but chart fills and badge color classes remain as inline hex strings and raw Tailwind utilities, changing the theme or adjusting a single color requires hunting down every call site.
+
+**Why it happens:** shadcn components use CSS variables natively. But Recharts does not — `fill` props require string hex or named colors. Developers add those inline and then replicate the same approach in badge class strings, gradient headers, and row highlights.
+
+**Consequences:**
+- A "pastel blue" may be `#93c5fd` in one chart, `blue-300` in a gradient, and `--chart-1` in another chart. They look similar but are not the same.
+- Adjusting saturation for accessibility requires touching 10+ places instead of 1.
+- Dark mode (even if not this milestone) is impossible to add cleanly later.
+
+**Detection warning signs:**
+- `grep` finds hex color strings (`#`) in component `.tsx` files
+- Tailwind color classes like `bg-pink-50`, `text-emerald-800` in component files alongside `bg-card`, `text-muted-foreground` in the same codebase
+- `PASTEL_COLORS` arrays defined per-component (`AvailableBalance.tsx` and `ExpenseBreakdown.tsx` each define their own separate array with partially overlapping values)
+
+**Prevention:**
+1. Define the full pastel palette as CSS variables in `index.css` — one variable per semantic purpose: `--color-income`, `--color-bills`, `--color-expenses`, etc.
+2. Map those variables into Tailwind's `@theme inline` block so `bg-income`, `text-bills` etc. work as utility classes.
+3. For Recharts fills, create a single exported constant array that references the CSS variable resolved values (use `getComputedStyle` or a constant palette object that is the single source of truth).
+4. Replace per-component `PASTEL_COLORS` arrays with imports from a central `lib/palette.ts`.
+
+**Phase:** Must be addressed in Phase 1 (design token foundation) before touching any components. Fixing it mid-polish causes re-work.
+
+---
+
+### Pitfall 2: Overriding shadcn Components by Editing Their Source Files
+
+**What goes wrong:** shadcn/ui is not a dependency — the components live in `src/components/ui/`. When developers want to change the default appearance of `Button`, `Card`, `Input`, etc., the temptation is to edit those source files directly (add default className values, change variant definitions, etc.).
+
+**Why it happens:** It feels like the "right" place — the code is right there, you own it, and `className` overrides in every call site feel verbose.
+
+**Consequences:**
+- If `shadcn add` is run later to add a new component or update an existing one, it overwrites your customizations or creates merge conflicts.
+- The diff between the upstream shadcn component and your version becomes invisible — future developers don't know what was intentionally changed vs. what is default shadcn behavior.
+- You lose the ability to reference shadcn docs accurately.
+
+**Detection warning signs:**
+- Any `className` default changed in `src/components/ui/*.tsx` files
+- Variant maps in `button.tsx`, `badge.tsx`, `card.tsx` expanded beyond the shadcn defaults
+
+**Prevention:**
+- Customize appearance exclusively through CSS variables in `index.css`. shadcn components read `--primary`, `--card`, `--border`, `--radius` etc. — those are the intended extension points.
+- For structural changes (adding a new button variant, a custom card subcomponent), create wrapper components like `src/components/ui/pastel-card.tsx` that compose shadcn primitives rather than modifying them.
+- Treat `src/components/ui/` as vendor code. Only modify it if the change would be appropriate to upstream.
+
+**Phase:** Establish this rule before any component work begins. A short note in `CLAUDE.md` prevents accidental violations.
+
+---
+
+### Pitfall 3: Duplicated InlineEditRow Logic Diverging During Polish
+
+**What goes wrong:** `BillsTracker.tsx`, `VariableExpenses.tsx`, and `DebtTracker.tsx` all contain a local `InlineEditRow` function with identical logic and structure. The visual polish work will need to change the edit affordance (hover state, active state, input styling, focus ring) in all three. If they remain separate, each will be polished independently and end up slightly different.
+
+**Why it happens:** The component was created inline as a local helper and never extracted. Since each file is self-contained it doesn't feel wrong — until the polish pass.
+
+**Consequences:**
+- Three components get slightly different hover colors, different input widths, different transition timings.
+- A bug fix or interaction change (e.g., adding an ESC key handler or optimistic update) must be applied three times.
+- Reviewers approve them one at a time and miss inconsistencies.
+
+**Detection warning signs:**
+- `grep -r "InlineEditRow"` returns multiple files
+- Visual comparison of Bills, Variable Expenses, and Debt tables reveals small inconsistencies in edit behavior
+
+**Prevention:**
+- Extract `InlineEditRow` to `src/components/InlineEditCell.tsx` before the polish phase begins.
+- Unify props interface; handle all three use cases (bills, variable expenses, debts are all "click to edit actual amount").
+- Polish the one component once.
+
+**Phase:** Phase 1 or early Phase 2, before any visual work on those table components.
+
+---
+
+### Pitfall 4: Chart Colors Not Connected to the Semantic Color System
+
+**What goes wrong:** Recharts charts (`PieChart` in `AvailableBalance`, `PieChart` in `ExpenseBreakdown`, `BarChart` in `VariableExpenses`) all use hardcoded hex palettes. The donut chart in `AvailableBalance` slices map to budget categories (bills, expenses, debts, savings, investments). If the color for "debts" in the donut is different from the color for "Debts" row in the `FinancialOverview` table, the user sees two different colors for the same concept on the same screen.
+
+**Why it happens:** The table rows use Tailwind classes (`bg-red-50`) while the chart uses an index-based array (`PASTEL_COLORS[index]`). There is no mapping from category type to a single canonical color.
+
+**Consequences:**
+- Cognitive load: user cannot use color as a navigation cue across widgets.
+- "Bills" is blue in the overview table header gradient, blue-300 in one chart, and whatever position it lands at in the pie.
+- The donut in `AvailableBalance` has no legend — users must already be confused about which slice is which.
+
+**Detection warning signs:**
+- Side-by-side view of `AvailableBalance` donut and `FinancialOverview` table with different colors for the same categories
+- `PASTEL_COLORS` arrays that do not reference a category-to-color map
+
+**Prevention:**
+- Define a `CATEGORY_COLORS` map in `lib/palette.ts`: `{ income: '#...', bill: '#...', variable_expense: '#...', debt: '#...', saving: '#...', investment: '#...' }`
+- All chart `fill` props, all badge class strings, all row background classes derive from this map.
+- Do not use index-based color arrays for category data — always key by category type.
+
+**Phase:** Phase 1 (design token foundation). Required before polishing any chart component.
+
+---
+
+### Pitfall 5: Polish Feels "Off" Because Layout Spacing Is Inconsistent, Not Just Colors
+
+**What goes wrong:** A common mistake in UI polish projects is focusing on colors and typography while leaving spacing inconsistent. Looking at the current layout: `DashboardPage` uses `p-6` and `gap-6`. `CardContent` sometimes uses `p-0` (for tables), sometimes `pt-4`, sometimes `pt-6`. `CardHeader` has gradient backgrounds but no consistent padding treatment relative to card content. Auth forms (`LoginPage`) have no background treatment — just `bg-background` which is white.
+
+**Why it happens:** Spacing decisions were made locally per component during initial development. The Tailwind utility model makes it easy to add `p-4` or `p-6` without thinking about global rhythm.
+
+**Consequences:**
+- Even if colors are right, the layout feels amateur because card headers have different internal padding, section gaps vary, and the auth page has no visual brand presence.
+- Dashboard looks like a collection of separate components rather than a unified screen.
+
+**Detection warning signs:**
+- `CardHeader` padding varies across components
+- `CardContent` uses `p-0` in some cards and `pt-4`/`pt-6` in others without a clear rule
+- Auth pages have no background color or brand element beyond the card itself
+- Sidebar lacks any visual weight or brand mark beyond plain text
+
+**Prevention:**
+- Define a spacing scale decision early: what is the standard gap between dashboard sections? Between card header and content? Between form fields?
+- Encode those decisions in either Tailwind config (spacing tokens) or a layout component.
+- Treat the auth pages as a first-class design surface — they are the first screen users see.
+
+**Phase:** Phase 2 (layout and structure), before polishing individual components.
+
+---
+
+## Moderate Pitfalls
+
+---
+
+### Pitfall 6: i18n Keys Missing for New UI Text Introduced During Polish
+
+**What goes wrong:** The polish phase will add new UI elements — empty states, tooltips, chart legends, aria labels, section descriptions, helper text. Each piece of text needs both `en.json` and `de.json` entries. During fast iteration it is easy to hardcode English strings directly in JSX.
+
+**Why it happens:** Adding `t('some.new.key')` requires updating two JSON files. Under time pressure developers skip it and plan to "add translations later."
+
+**Consequences:**
+- German users see raw English strings or key names like `dashboard.expenseTooltip`
+- Text added without translation keys becomes invisible debt — only discovered when someone switches the language
+
+**Detection warning signs:**
+- String literals in JSX that are not wrapped in `t()`: `
No transactions yet
`
+- `t()` calls referencing keys that exist in `en.json` but not `de.json`
+
+**Prevention:**
+- As a discipline: never commit a new UI string without both translation files updated.
+- At the start of the milestone, run a diff between `en.json` and `de.json` key sets to verify they're in sync.
+- For every new UI element added during polish, add the translation key immediately, even if the German translation is a placeholder.
+
+**Phase:** Ongoing across all phases. Establish the discipline at the start.
+
+---
+
+### Pitfall 7: Recharts Tooltip and Legend Styling Not Customized — Default Gray Box Breaks Pastel Aesthetic
+
+**What goes wrong:** Recharts renders its `` as a white box with gray border by default. Its `