diff --git a/.planning/research/ARCHITECTURE.md b/.planning/research/ARCHITECTURE.md new file mode 100644 index 0000000..16a362b --- /dev/null +++ b/.planning/research/ARCHITECTURE.md @@ -0,0 +1,452 @@ +# Architecture Research + +**Domain:** Personal finance dashboard UI — React SPA overhaul +**Researched:** 2026-03-16 +**Confidence:** HIGH (existing codebase is fully inspected; patterns are grounded in Radix/shadcn/Recharts official docs) + +--- + +## Standard Architecture + +### System Overview + +The existing three-tier architecture (Pages → Hooks → Supabase) is sound and must be preserved. The UI overhaul introduces a new layer of **dashboard-specific view components** that sit between pages and the primitive shadcn/ui atoms. Nothing touches hooks or the library layer. + +``` +┌───────────────────────────────────────────────────────────────┐ +│ Pages Layer │ +│ DashboardPage CategoriesPage BudgetDetailPage ... │ +│ (routing, data loading, layout composition) │ +├───────────────────────────────────────────────────────────────┤ +│ View Components Layer [NEW] │ +│ ┌─────────────────┐ ┌──────────────┐ ┌─────────────────┐ │ +│ │ DashboardContent│ │CategorySection│ │ ChartPanel │ │ +│ │ (hybrid layout) │ │(collapsible) │ │ (chart wrappers)│ │ +│ └─────────────────┘ └──────────────┘ └─────────────────┘ │ +│ ┌─────────────────┐ ┌──────────────┐ ┌─────────────────┐ │ +│ │ SummaryStrip │ │ BudgetTable │ │ PageShell │ │ +│ │ (KPI cards row) │ │ (line items) │ │ (consistent │ │ +│ └─────────────────┘ └──────────────┘ │ header+CTA) │ │ +│ └─────────────────┘ │ +├───────────────────────────────────────────────────────────────┤ +│ Primitive UI Layer (shadcn/ui) │ +│ Card Button Table Dialog Select Collapsible Badge ... │ +├───────────────────────────────────────────────────────────────┤ +│ Hooks Layer [UNCHANGED] │ +│ useBudgets useBudgetDetail useCategories useAuth ... │ +├───────────────────────────────────────────────────────────────┤ +│ Library Layer [UNCHANGED] │ +│ supabase.ts types.ts format.ts palette.ts utils.ts │ +│ index.css (@theme tokens — EXTEND for new color tokens) │ +└───────────────────────────────────────────────────────────────┘ +``` + +**The constraint is strict:** hooks and library are read-only during this milestone. All UI overhaul changes land in `src/pages/`, `src/components/`, and `src/index.css` only. + +--- + +### Component Responsibilities + +| Component | Responsibility | Typical Implementation | +|-----------|----------------|------------------------| +| `DashboardPage` | Find current month budget, render shell | Unchanged outer page; delegates to `DashboardContent` | +| `DashboardContent` | Hybrid layout orchestration | Calls `useBudgetDetail`; computes derived data with `useMemo`; renders SummaryStrip + charts + CategorySections | +| `SummaryStrip` | Three KPI cards (income, expenses, balance) | Grid of `StatCard` components; color-coded balance | +| `StatCard` | Single KPI display unit | shadcn Card with title, large number, optional trend indicator | +| `ChartPanel` | Houses all charts in responsive grid | Two-column grid on desktop: income bar chart left, expense donut right, spend horizontal bar full-width below | +| `IncomeBarChart` | Budgeted vs actual income vertical bar | Recharts `BarChart` wrapped in `ChartContainer` with `ChartConfig` | +| `ExpenseDonutChart` | Expense category breakdown donut | Recharts `PieChart` with `innerRadius`/`outerRadius` + custom legend | +| `SpendBarChart` | Horizontal budget vs actual by category type | Recharts `BarChart layout="vertical"` | +| `CategorySection` | Collapsible group for one category type | Radix `Collapsible.Root` wrapping a header row + `BudgetLineItems` | +| `CategorySectionHeader` | Always-visible row: type label, color dot, group totals, chevron | Trigger for the collapsible; shows budgeted/actual/diff inline | +| `BudgetLineItems` | Table of individual line items inside a section | shadcn `Table`; thin wrapper around existing `InlineEditCell` / `DifferenceCell` atoms | +| `PageShell` | Consistent page header with title + primary CTA | Reusable wrapper used by every page; enforces padding, heading size, CTA slot | +| `AppLayout` | Sidebar navigation shell | Minor visual refresh only; structure unchanged | + +--- + +## Recommended Project Structure + +The existing structure is well-organized. The overhaul adds a `dashboard/` subfolder and a `shared/` subfolder under components — no reorganization of hooks or lib. + +``` +src/ +├── components/ +│ ├── ui/ # shadcn primitives (do not modify) +│ │ └── collapsible.tsx # ADD — Radix Collapsible primitive +│ ├── dashboard/ # ADD — dashboard-specific view components +│ │ ├── DashboardContent.tsx # hybrid layout orchestrator +│ │ ├── SummaryStrip.tsx # KPI cards row +│ │ ├── StatCard.tsx # single KPI card +│ │ ├── ChartPanel.tsx # chart grid container +│ │ ├── IncomeBarChart.tsx # budgeted vs actual income bar +│ │ ├── ExpenseDonutChart.tsx # donut + legend +│ │ ├── SpendBarChart.tsx # horizontal budget vs actual +│ │ ├── CategorySection.tsx # collapsible category group +│ │ └── BudgetLineItems.tsx # line-item table inside section +│ ├── shared/ # ADD — cross-page reusable components +│ │ └── PageShell.tsx # consistent page header + CTA slot +│ ├── AppLayout.tsx # MODIFY — visual refresh only +│ └── QuickAddPicker.tsx # unchanged +├── pages/ # MODIFY — swap DashboardContent import; apply PageShell +│ ├── DashboardPage.tsx +│ ├── BudgetDetailPage.tsx +│ ├── BudgetListPage.tsx +│ ├── CategoriesPage.tsx +│ ├── TemplatePage.tsx +│ ├── QuickAddPage.tsx +│ ├── SettingsPage.tsx +│ ├── LoginPage.tsx +│ └── RegisterPage.tsx +├── hooks/ # UNCHANGED +├── lib/ +│ ├── palette.ts # UNCHANGED — CSS vars already defined +│ └── ... # everything else unchanged +├── i18n/ +│ ├── en.json # ADD new translation keys +│ └── de.json # ADD new translation keys +└── index.css # ADD semantic color tokens if needed +``` + +### Structure Rationale + +- **`components/dashboard/`:** All dashboard-specific view components are co-located. They have no meaning outside the dashboard, so they do not belong in `shared/`. Avoids polluting the top-level components directory. +- **`components/shared/`:** `PageShell` is the one genuinely cross-page component introduced by this milestone. Keeping it separate signals that it is intentionally reusable, not page-specific. +- **`components/ui/collapsible.tsx`:** The Radix Collapsible primitive is not yet in the project (inspected file list confirms absence). It must be added via `npx shadcn@latest add collapsible` before building `CategorySection`. + +--- + +## Architectural Patterns + +### Pattern 1: Derived Data via `useMemo` in DashboardContent + +**What:** All computed values — category group totals, chart data arrays, KPI numbers — are derived in one place (`DashboardContent`) using `useMemo`, then passed as plain props to presentational child components. Child components never call hooks or perform calculations themselves. + +**When to use:** Any time a value depends on `items` array from `useBudgetDetail`. Centralizing derivation means one cache invalidation (after a budget item update) triggers one recalculation, and all children rerender from the same consistent snapshot. + +**Trade-offs:** Slightly more props-passing verbosity. Benefit: children are trivially testable pure components. + +**Example:** +```typescript +// DashboardContent.tsx +const { budget, items } = useBudgetDetail(budgetId) + +const totals = useMemo(() => { + const income = items + .filter(i => i.category?.type === "income") + .reduce((sum, i) => sum + i.actual_amount, 0) + const expenses = items + .filter(i => i.category?.type !== "income") + .reduce((sum, i) => sum + i.actual_amount, 0) + return { income, expenses, balance: income - expenses + (budget?.carryover_amount ?? 0) } +}, [items, budget?.carryover_amount]) + +const groupedItems = useMemo(() => + CATEGORY_TYPES.map(type => ({ + type, + items: items.filter(i => i.category?.type === type), + budgeted: items.filter(i => i.category?.type === type).reduce((s, i) => s + i.budgeted_amount, 0), + actual: items.filter(i => i.category?.type === type).reduce((s, i) => s + i.actual_amount, 0), + })).filter(g => g.items.length > 0), +[items]) + +// Pass totals and groupedItems as props; child components are pure +``` + +### Pattern 2: Collapsible Category Sections via Radix Collapsible + +**What:** Each category group (income, bills, variable expenses, debt, savings, investment) is wrapped in a `Collapsible.Root`. The always-visible trigger row shows the category label, color dot, and group-level budget/actual/difference totals. The collapsible content reveals the individual line-item table. + +**When to use:** The dashboard hybrid view — users need the summary at a glance without scrolling through every line item. Opening a section is an explicit drill-down action. + +**Trade-offs:** Adds `open` state per section. Use `useState` per section (not global state — there are at most 6 sections). Do not persist open state in localStorage for v1; the sections should open fresh on each visit so the summary view is the default. + +**Example:** +```typescript +// CategorySection.tsx +import { Collapsible, CollapsibleContent, CollapsibleTrigger } + from "@/components/ui/collapsible" +import { ChevronDown } from "lucide-react" +import { useState } from "react" + +interface CategorySectionProps { + type: CategoryType + budgeted: number + actual: number + items: BudgetItem[] + currency: string +} + +export function CategorySection({ type, budgeted, actual, items, currency }: CategorySectionProps) { + const [open, setOpen] = useState(false) + const { t } = useTranslation() + + return ( + + + + + + + + + ) +} +``` + +### Pattern 3: shadcn ChartContainer + ChartConfig for All Charts + +**What:** Wrap every Recharts chart in shadcn's `ChartContainer` component. Define colors and labels in a `ChartConfig` object that references existing CSS variable tokens from `index.css` (`var(--color-income)`, `var(--color-bill)`, etc.). Do not hardcode hex values inside chart components. + +**When to use:** All three chart types (bar, horizontal bar, donut). This ensures charts automatically theme with the design system and dark mode works at zero extra cost. + +**Trade-offs:** Requires adding the shadcn `chart` component (`npx shadcn@latest add chart`). Minor wrapper overhead, but the CSS variable binding and tooltip consistency is worth it. + +**Example:** +```typescript +// IncomeBarChart.tsx +import { ChartContainer, ChartTooltip, ChartTooltipContent } + from "@/components/ui/chart" +import { BarChart, Bar, XAxis, YAxis, CartesianGrid } from "recharts" + +const chartConfig = { + budgeted: { label: "Budgeted", color: "var(--color-income)" }, + actual: { label: "Actual", color: "var(--color-income)" }, +} satisfies ChartConfig + +// data: [{ month: "March", budgeted: 3000, actual: 2850 }] +export function IncomeBarChart({ data, currency }: Props) { + return ( + + + + + formatCurrency(v, currency)} /> + } /> + + + + + ) +} +``` + +### Pattern 4: PageShell for Consistent Page Headers + +**What:** A `PageShell` component accepts `title`, `description` (optional), and `action` (optional ReactNode slot for a primary CTA button). Every page wraps its top section in `PageShell`. This enforces a consistent heading size, spacing, and CTA placement across the entire app refresh. + +**When to use:** All 9 pages in the overhaul. Any new page added in future milestones should also use it. + +**Trade-offs:** Adds one wrapper per page. The benefit is that a single visual change to the page header propagates everywhere without hunting through 9 files. + +**Example:** +```typescript +// 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} +
+ ) +} +``` + +--- + +## Data Flow + +### Dashboard Read Flow + +``` +DashboardPage renders + ↓ +useBudgets() → finds current month budget → passes budgetId prop + ↓ +DashboardContent mounts + ↓ +useBudgetDetail(budgetId) → TanStack Query cache or Supabase fetch + ↓ data arrives +useMemo recalculates: totals, groupedItems, chartData + ↓ +Props flow DOWN to pure presentational children: + SummaryStrip(totals) + ChartPanel(chartData, currency) + ├── IncomeBarChart(barData) + ├── ExpenseDonutChart(pieData) + └── SpendBarChart(horizontalData) + CategorySection[] (groupedItems, per-type items) + └── BudgetLineItems(items, currency) +``` + +### Budget Item Edit Flow (unchanged, flows back up) + +``` +InlineEditCell: user types new actual_amount + ↓ +onCommit → updateItem.mutateAsync({ id, budgetId, actual_amount }) + ↓ +Supabase updates budget_items row + ↓ +onSuccess: queryClient.invalidateQueries(["budgets", budgetId, "items"]) + ↓ +useBudgetDetail re-fetches items + ↓ +DashboardContent useMemo recalculates all derived values + ↓ +ALL children rerender with consistent new data +``` + +### State Management (what lives where) + +| State | Location | Why | +|-------|----------|-----| +| Budget and items data | TanStack Query cache | Server state, must survive component unmounts | +| Collapsible open/closed | `useState` in each `CategorySection` | Purely local UI state; 6 booleans maximum | +| Chart tooltip hover | Recharts internal | Library-managed interaction state | +| Dialog open/closed | `useState` in page components | Unchanged from current pattern | +| Currency, locale | `Profile` via Supabase → hooks | Read from `budget.currency`; no separate state | + +--- + +## Scaling Considerations + +This is a personal finance app for a single authenticated user at a time. Scale is not a concern for rendering. The relevant concern is **perceived performance** on the dashboard when `items` is large (100+ line items). + +| Scale | Architecture Adjustment | +|-------|--------------------------| +| <50 items (normal) | No optimization needed — current useMemo pattern is fast | +| 50-200 items | useMemo already handles this — O(n) passes are negligible | +| 200+ items | Consider React.memo on CategorySection to skip unchanged sections; still no virtualization needed | +| Dashboard load time | TanStack Query 5-min staleTime means instant rerender on navigate-back; no change required | + +--- + +## Anti-Patterns + +### Anti-Pattern 1: Deriving Chart Data Inside Chart Components + +**What people do:** Put `items.filter(...).reduce(...)` directly inside `IncomeBarChart` or `ExpenseDonutChart`, passing the raw `items` array from `useBudgetDetail` as a prop. + +**Why it's wrong:** Each chart component recalculates from scratch. If `items` reference changes (after a mutation), all three charts recalculate independently. Chart components become impure, harder to test, and cannot be reused with different data shapes without changing their internals. + +**Do this instead:** Derive all chart data in `DashboardContent` with `useMemo`. Pass prepared `barData`, `pieData`, `horizontalData` arrays to each chart. Charts receive typed data arrays and render only. + +--- + +### Anti-Pattern 2: Hardcoding Colors Inside Chart Components + +**What people do:** Paste hex values like `fill="#4ade80"` into `` and `` components to match the design. + +**Why it's wrong:** The existing `index.css` already defines category colors as OKLCH CSS variables (`--color-income`, `--color-bill`, etc.) and `palette.ts` maps them to `var(--color-income)` etc. Hardcoding breaks dark mode adaptation, creates a second source of truth, and means a palette change requires editing multiple files. + +**Do this instead:** Always reference `categoryColors[type]` from `palette.ts` (which returns the CSS variable string) for both chart fills and UI color dots. For shadcn `ChartContainer`, pass `color: categoryColors[type]` in the `ChartConfig` object. + +--- + +### Anti-Pattern 3: One Monolithic DashboardContent Component + +**What people do:** Add all new dashboard sections — summary cards, three charts, six collapsible sections, QuickAdd button — directly into one large `DashboardContent.tsx` that becomes 400+ lines. + +**Why it's wrong:** The existing `DashboardContent` is already 200 lines with just two charts and progress bars. A full hybrid dashboard with three chart types and six collapsible sections will exceed 600 lines inline, making it impossible to review, test, or modify individual sections without reading the whole file. + +**Do this instead:** Extract into the component tree defined above. `DashboardContent` orchestrates layout and owns derived data. Each distinct visual section (`SummaryStrip`, `ChartPanel`, `CategorySection`) is its own file. The rule: if a block of JSX has a distinct visual purpose, it gets its own component. + +--- + +### Anti-Pattern 4: Using shadcn Accordion Instead of Collapsible for Category Sections + +**What people do:** Reach for the `Accordion` component (which is already in some shadcn setups) to build collapsible category sections because it looks similar. + +**Why it's wrong:** Accordion by default enforces "only one open at a time" (type="single") or requires explicit `type="multiple"` with `collapsible` prop to allow free open/close. For budget sections, users may want to compare two categories side-by-side with both open simultaneously. Using individual `Collapsible` per section gives full independent control without fighting Accordion's root-state coordination. + +**Do this instead:** Use Radix `Collapsible` (shadcn wrapper) on each `CategorySection` with independent `useState`. Six independent booleans are trivially managed. + +--- + +### Anti-Pattern 5: Modifying Hooks or Supabase Queries + +**What people do:** Add derived fields or aggregations to the `useBudgetDetail` return value, or add new Supabase query fields, because it seems convenient during the UI overhaul. + +**Why it's wrong:** Explicitly out of scope (PROJECT.md: "No Supabase schema changes — UI-only modifications"). Any hook changes risk breaking `BudgetDetailPage` and other consumers. The data model is sufficient — all needed aggregations can be computed with `useMemo` from the existing `items` array. + +**Do this instead:** Keep hooks read-only. All computation lives in the presentation layer via `useMemo`. + +--- + +## Integration Points + +### External Services + +| Service | Integration | Notes | +|---------|-------------|-------| +| Supabase | Unchanged — hooks layer handles all DB calls | No new RPC calls, no schema changes | +| Recharts | Chart primitives — wrap with `ChartContainer` | Requires adding shadcn `chart` component | +| Radix UI | `Collapsible` primitive — add via shadcn CLI | Not yet in project; must be added before CategorySection work | +| i18next | All new UI text needs keys in `en.json` and `de.json` | Add keys before rendering any new text; no runtime fallback acceptable | + +### Internal Boundaries + +| Boundary | Communication | Notes | +|----------|---------------|-------| +| `DashboardContent` ↔ chart components | Props only — typed data arrays + currency string | Charts are pure; they do not call hooks | +| `DashboardContent` ↔ `CategorySection` | Props only — grouped items, budgeted/actual totals, currency, mutation handlers | Mutation handlers passed down from `DashboardContent` which owns `useBudgets()` mutations | +| `CategorySection` ↔ `BudgetLineItems` | Props only — items array, currency, type | `BudgetLineItems` is a thin table wrapper; all mutations are callbacks from parent | +| `PageShell` ↔ all pages | Props (title, action slot, children) | No state shared; purely compositional | +| `index.css` @theme tokens ↔ components | CSS variables via `var(--color-X)` and `palette.ts` | Single source of truth for all color; never duplicate in component style props | + +--- + +## Build Order Implications + +Components have dependencies that dictate implementation order: + +1. **`components/ui/collapsible.tsx`** — Add via `npx shadcn@latest add collapsible`. Required by `CategorySection`. Do first. +2. **`components/ui/chart.tsx`** — Add via `npx shadcn@latest add chart`. Required by all chart components. Do first. +3. **`shared/PageShell.tsx`** — No dependencies. Build early; apply to all pages as each is refreshed. +4. **`StatCard` + `SummaryStrip`** — Only depends on `formatCurrency` and Tailwind. Build second after primitives. +5. **Chart components** (`IncomeBarChart`, `ExpenseDonutChart`, `SpendBarChart`) — Depend on `ChartContainer`. Build after step 2. +6. **`ChartPanel`** — Composes the three chart components. Build after step 5. +7. **`BudgetLineItems`** — Refactored from existing `BudgetDetailPage` table code; existing `InlineEditCell` and `DifferenceCell` are reusable as-is. +8. **`CategorySection`** — Depends on `Collapsible` primitive and `BudgetLineItems`. Build after steps 1 and 7. +9. **`DashboardContent`** — Orchestrates everything. Build last, wiring together all children. Replace the existing `DashboardContent` function in `DashboardPage.tsx`. +10. **Non-dashboard page refreshes** — Apply `PageShell` + visual refresh to remaining 7 pages. Independent of dashboard work; can be done in any order. + +--- + +## Sources + +- Radix UI Collapsible: https://www.radix-ui.com/primitives/docs/components/collapsible (HIGH confidence — official docs) +- shadcn/ui Chart component: https://ui.shadcn.com/docs/components/radix/chart (HIGH confidence — official docs) +- shadcn/ui Accordion: https://ui.shadcn.com/docs/components/radix/accordion (HIGH confidence — official docs) +- Tailwind CSS v4 @theme tokens: https://tailwindcss.com/docs/theme (HIGH confidence — official docs) +- React useMemo: https://react.dev/reference/react/useMemo (HIGH confidence — official docs) +- Existing codebase: inspected `src/` fully — `DashboardPage.tsx`, `BudgetDetailPage.tsx`, `CategoriesPage.tsx`, `AppLayout.tsx`, `palette.ts`, `types.ts`, `index.css`, `useBudgets.ts` (HIGH confidence — primary source) + +--- + +*Architecture research for: SimpleFinanceDash UI overhaul — React + Tailwind + shadcn/ui + Recharts* +*Researched: 2026-03-16* diff --git a/.planning/research/FEATURES.md b/.planning/research/FEATURES.md new file mode 100644 index 0000000..9c58e49 --- /dev/null +++ b/.planning/research/FEATURES.md @@ -0,0 +1,244 @@ +# Feature Research + +**Domain:** Personal finance budget dashboard (UI presentation layer overhaul) +**Researched:** 2026-03-16 +**Confidence:** HIGH — grounded in competitor analysis (YNAB, Empower), industry design guides, and direct codebase inspection + +--- + +## Context + +This research covers the **presentation layer** only. Backend schema and data model are frozen. The question is: what UI/UX features make a personal finance dashboard feel polished rather than basic, and which of those apply to this project's existing data model (income, bills, variable expenses, debt, savings, investments — budgeted vs actual)? + +The existing stack is React 19 + Tailwind CSS 4 + Recharts + shadcn/ui. All chart capabilities already exist in Recharts; this research identifies how to use them well. + +--- + +## Feature Landscape + +### Table Stakes (Users Expect These) + +Features users assume exist. Missing these = product feels incomplete. + +| Feature | Why Expected | Complexity | Notes | +|---------|--------------|------------|-------| +| Summary KPI cards at the top | Every finance dashboard leads with top-line numbers (income, total spend, balance). Users scan these first — it's the entry point to understanding the month. | LOW | Already exists but needs richer visual treatment: larger numbers, colored icons, clearer hierarchy | +| Green/red semantic color coding for over/under budget | Finance convention: green = on track, red = over budget. Violating this creates cognitive friction. | LOW | Already exists on progress bars and difference cells; must be consistent across all new chart types | +| Donut/pie chart for expense category breakdown | Standard visualization for "how is spending distributed." Users expect to see the breakdown at a glance. | LOW | Existing pie is plain; needs inner radius (donut), center total label, and richer color fills | +| Budget vs actual per category | The core comparison every budget tool offers. Users come here specifically to compare what they planned to what they actually spent. | MEDIUM | Existing progress bars satisfy this minimally; new grouped/horizontal bar charts will fulfill it properly | +| Inline value editing on budget detail | Users need to log actuals inline — no one wants a separate form for every line item. | LOW | Already implemented in BudgetDetailPage as InlineEditCell; keep and refine | +| Loading state that reflects page structure | If the page renders blank or flashes, users distrust the data or think it's broken. | LOW | Currently returns `null` during load — skeleton cards matching the final layout are expected | +| Empty state with actionable guidance | A blank dashboard for a new month should tell the user what to do next, not just show nothing. | LOW | Currently shows plain muted text; needs a CTA card pattern | +| Tabular-numeral formatting throughout | Financial amounts must use tabular (monospaced number) alignment so columns read correctly. | LOW | Already applied via `tabular-nums`; extend to all new components | +| Color-coded category identity | Users build a mental map: bills are orange, savings are blue. Consistent color per category type is required everywhere — charts, tables, badges. | LOW | Palette already defined in `index.css` and `palette.ts`; extend consistently to new components | +| Collapsible / grouped sections per category type | Budget detail and dashboard both group items by type. Users expect to expand/collapse groups, especially with many line items. | MEDIUM | BudgetDetailPage already groups by type; dashboard needs collapsible inline sections as new feature | +| Month navigation on the dashboard | Users want to check a prior month without navigating away. "Looking at last month" is a top-3 use case for any budget tool. | MEDIUM | Not in current dashboard (auto-resolves current month only); needs month selector | +| Totals row / footer per section | Standard pattern from spreadsheets. Each category group needs a sub-total visible without counting rows. | LOW | Already in BudgetDetailPage table footer; needs equivalent on dashboard sections | +| Consistent design language across all pages | Auth pages, settings, categories, and budget list must feel like the same product as the dashboard. Inconsistency signals amateur work. | HIGH | Currently only dashboard is styled; all other pages need the same card/color/typography treatment | + +### Differentiators (Competitive Advantage) + +Features that set the product apart. Not required, but valued. + +| Feature | Value Proposition | Complexity | Notes | +|---------|-------------------|------------|-------| +| Grouped bar chart: income budgeted vs actual | Directly answers "Did I earn what I expected this month?" in a single visual. Most basic dashboards skip this, showing only expense charts. | MEDIUM | Recharts `BarChart` with two `Bar` components (budgeted / actual); use category colors | +| Horizontal bar chart: spend by category type (budget vs actual) | Lets users scan over-budget categories at a glance. Horizontal orientation makes labels readable without rotation. | MEDIUM | Recharts `BarChart layout="vertical"` with `XAxis type="number"` and `YAxis type="category"` | +| Donut chart with center total label | Center label turns the chart from decorative into informational — shows total spend while the ring shows distribution. YNAB and Empower both do this. | LOW | Recharts `Label` component inside `Pie` using `viewBox` coordinates for positioned text | +| Active sector hover highlight on donut | Hovering a slice expands it slightly and shows the category name + amount in a tooltip. Feels interactive and polished vs a static chart. | LOW | Recharts `activeShape` with expanded `outerRadius` — supported natively | +| Variance indicator (delta arrows) | Show "▲ 12% over" or "▼ 5% under" beside actual amounts on summary cards and section headers. Transforms raw numbers into directional insight. | LOW | Computed from budgeted/actual; render as small colored badge or arrow icon | +| Accent-colored card borders / icon containers | Rich visual style: each category section gets its palette color as a left border accent or icon container fill. Breaks the sea-of-grey-cards look. | LOW | Tailwind `border-l-4` or `bg-[color]/10` container with category CSS variable | +| Section collapse persisted in localStorage | Remembering which sections are open avoids users re-expanding the same sections every visit. Small touch that signals quality. | LOW | Simple `useState` + `localStorage` read/write; wrap in a `useCollapse` hook | +| Carryover amount visible on dashboard | The budget model already tracks `carryover_amount`. Showing it alongside the available balance makes the "running total" story clear. | LOW | Surface as a fourth summary card or sub-label on the balance card | +| Skeleton loading that mirrors the real layout | Animated skeleton cards/charts during load feel like the app is fast and predictable. Blank screens feel broken. | LOW | shadcn/ui `Skeleton` component already available; wrap summary cards and chart containers | +| Page header with budget month name | Every page in the app should show context: "March 2026 Budget." Users confirm they're looking at the right period immediately. | LOW | Format `budget.start_date` as "Month YYYY" in the page heading | + +### Anti-Features (Commonly Requested, Often Problematic) + +Features that seem good but create problems in this context. + +| Feature | Why Requested | Why Problematic | Alternative | +|---------|---------------|-----------------|-------------| +| Trend / multi-month charts | Users always ask "can I see my spending over 6 months?" | Requires multi-budget data aggregation, a new query shape, and a new chart layout — out of scope for a UI-only overhaul. Adding it half-baked is worse than not adding it. | Explicitly defer to a future milestone. Put a placeholder card with "Coming soon" if needed. | +| Dark mode toggle | Professional apps always have a dark mode. | Tailwind CSS 4 dark mode via class strategy is possible, but requires auditing every component's contrast, chart colors, and skeleton states. Doing it properly doubles testing surface. The OKLCH color system is set up with dark mode in mind, but no dark palette is currently defined. | Define as a future phase deliverable. Keep the existing light-only approach consistent and polished first. | +| Real-time sync / live updates | "What if two tabs are open?" | No backend changes allowed this milestone. Supabase realtime would require schema changes and subscription setup. The current TanStack Query polling pattern is sufficient. | Rely on query invalidation on mutation — already in place. | +| Drag-to-reorder line items | Power users want custom sort order | Category `sort_order` field exists, but updating it requires PATCH calls and animation logic. No backend changes in scope. | Keep current `sort_order` from DB; do not expose drag handles this milestone. | +| Glassmorphism / heavy blur effects | Trendy, visually striking | `backdrop-blur` on many elements is GPU-heavy and can degrade performance on lower-end machines. Also risks reducing text readability on charts. Misapplied, it obscures data rather than enhancing it. | Use solid cards with subtle colored-border accents instead. Reserve `backdrop-blur` for a single hero element if at all. | +| AI / natural language budget insights | Feels modern, seen in apps like Copilot | Requires an AI backend, a new API integration, and user data being sent to a third-party service — none of which are part of a UI-only overhaul. | Surface static insights from computed data instead: "You spent 15% more on bills than budgeted this month." Computed in the frontend, no AI needed. | +| Infinite scroll on budget list | "I have many months of budgets" | Adds complexity to the budget list query and requires scroll position management. The budget list is unlikely to have more than 24 entries in a year of use. | Standard paginated or full list with a search/filter input is sufficient. | + +--- + +## Feature Dependencies + +``` +Donut Chart with Center Label + └──requires──> Category color system (already exists) + └──requires──> Recharts Label component in Pie (Recharts native) + +Horizontal Bar Chart (budget vs actual by type) + └──requires──> Category color system (already exists) + └──requires──> Grouped data by category type (already in DashboardContent) + +Grouped Bar Chart (income budget vs actual) + └──requires──> Income items filtered from BudgetItems (already in DashboardContent) + +Collapsible Dashboard Sections + └──requires──> Category group data (already derived) + └──enhances──> Section collapse persistence (localStorage) + └──enhances──> Totals row per section (already exists in BudgetDetailPage) + +Skeleton Loading + └──requires──> Final layout structure (must design layout first) + └──depends-on──> Summary cards layout (build cards first, then wrap in Skeleton) + +Month Navigation on Dashboard + └──requires──> Budgets list query (already in useBudgets) + └──conflicts──> Auto-resolve current month (replace with explicit selection) + +Consistent Design Language (all pages) + └──depends-on──> Dashboard redesign being finished first (establishes the design token/component pattern) + └──applies-to──> LoginPage, RegisterPage, CategoriesPage, TemplatePage, BudgetListPage, BudgetDetailPage, QuickAddPage, SettingsPage + +Variance Indicators (delta arrows) + └──requires──> budgeted_amount and actual_amount both available (already on BudgetItem) + └──enhances──> Summary cards (add % change sub-label) + └──enhances──> Collapsible section headers (show group variance at a glance) +``` + +### Dependency Notes + +- **Skeleton loading requires final layout:** Don't implement skeleton states until the target card/chart layout is finalized — skeleton shape must mirror real content shape. +- **All-pages redesign depends on dashboard:** The dashboard establishes the color system application patterns (accent borders, card styles, typography scale) that all other pages will inherit. +- **Month navigation conflicts with auto-resolve:** The current pattern auto-finds the budget for the current calendar month. Adding a selector means the dashboard must hold local `selectedBudgetId` state rather than computing it. +- **Donut center label requires Recharts Label:** shadcn/ui's chart system supports this natively via the `Label` component inside `Pie` using `viewBox` coordinates. No new library needed. + +--- + +## MVP Definition + +This is a UI overhaul milestone, not a greenfield product. "MVP" here means: what must ship for the redesign to feel complete and polished? + +### Launch With (v1 — Dashboard Overhaul) + +- [ ] Summary cards with richer visual treatment (larger numbers, semantic color on balance, variance badge) — foundation of the dashboard +- [ ] Donut chart with center total label and active sector hover — replaces existing flat pie +- [ ] Horizontal bar chart (budget vs actual by category type) — new chart, satisfies the key "am I on track" question +- [ ] Grouped bar chart (income budget vs actual) — completes the three-chart suite from the reference +- [ ] Collapsible inline sections per category group on dashboard (with line items + group totals) — replaces current progress bars +- [ ] Skeleton loading states for cards and charts — removes the jarring blank-then-rendered experience +- [ ] Month navigation control (budget period selector) on dashboard — without this the dashboard is locked to current month only + +### Add After Dashboard Phase (v1.x — Full App Polish) + +- [ ] Consistent card/color design applied to BudgetDetailPage — users navigate here from the dashboard; it must match +- [ ] Consistent design applied to BudgetListPage — entry point from nav, feels disconnected from dashboard without polish +- [ ] Consistent design applied to CategoriesPage and TemplatePage — setup pages; lower urgency but visible gap +- [ ] Consistent design applied to LoginPage and RegisterPage — first impression; polish matters +- [ ] Consistent design applied to QuickAddPage and SettingsPage — utility pages; can ship slightly after core flows +- [ ] Section collapse state persisted to localStorage — nice-to-have UX polish + +### Future Consideration (v2+) + +- [ ] Trend charts over multiple months — deferred explicitly in PROJECT.md out-of-scope +- [ ] Dark mode — foundational work (OKLCH variables) exists but needs full audit and dark palette definition +- [ ] AI-derived spending insights — requires backend changes; no scope here +- [ ] Drag-to-reorder categories — requires sort_order mutation support + +--- + +## Feature Prioritization Matrix + +| Feature | User Value | Implementation Cost | Priority | +|---------|------------|---------------------|----------| +| Summary cards — richer visual treatment | HIGH | LOW | P1 | +| Donut chart with center label + active hover | HIGH | LOW | P1 | +| Horizontal bar chart (spend vs budget by type) | HIGH | MEDIUM | P1 | +| Grouped bar chart (income budget vs actual) | HIGH | MEDIUM | P1 | +| Collapsible dashboard sections with line items | HIGH | MEDIUM | P1 | +| Skeleton loading states | MEDIUM | LOW | P1 | +| Month navigation on dashboard | HIGH | MEDIUM | P1 | +| Consistent design on BudgetDetailPage | HIGH | MEDIUM | P1 | +| Consistent design on BudgetListPage | MEDIUM | LOW | P2 | +| Variance indicators (delta arrows/badges) | MEDIUM | LOW | P2 | +| Accent-colored category section borders | MEDIUM | LOW | P2 | +| Carryover amount visible on dashboard | MEDIUM | LOW | P2 | +| Consistent design on auth/settings/utility pages | LOW | MEDIUM | P2 | +| Section collapse persisted in localStorage | LOW | LOW | P3 | +| Empty state with action guidance | MEDIUM | LOW | P2 | + +**Priority key:** +- P1: Must have for the overhaul to feel complete +- P2: Should have; include in same milestone if effort allows +- P3: Nice to have; add as polish after core delivery + +--- + +## Competitor Feature Analysis + +| Feature | YNAB | Empower (formerly Personal Capital) | Our Approach | +|---------|------|--------------------------------------|--------------| +| Summary KPI cards | Yes — "Available" balance prominent | Yes — net worth headline | 3 cards: Income / Expenses / Balance | +| Budget vs actual bars | Yes — horizontal category bars with color (green/yellow/red) | Cash flow bars | Horizontal bar chart per category type | +| Donut / pie chart | No (YNAB uses different visualization) | Yes — allocation donut for investments | Donut with center total, colored by category type | +| Collapsible grouped sections | Yes — master categories expand to show sub-categories | No — flat list | Collapsible per category type (income, bills, etc.) | +| Inline editing | Yes — click amount to edit | No | Keep existing InlineEditCell pattern, refine styling | +| Color-coded categories | Yes — status colors (green/yellow/red) | Category colors for accounts | Per-type semantic color (income=green, debt=red/pink, etc.) | +| Month/period navigation | Yes — budget period selector in sidebar | Yes — date range selector | Month selector on dashboard | +| Skeleton loading | Yes — YNAB shows skeleton on load | Yes | Skeleton cards + chart placeholders | +| Variance / delta indicators | Yes — shows "over by $X" inline | Yes — shows gain/loss % | Variance badge on summary cards and section headers | + +--- + +## Chart Design Notes (Recharts-Specific) + +These translate research findings into concrete implementation guidance for the Recharts stack. + +**Donut chart:** +- Use `innerRadius={60}` to `innerRadius={72}` for a modern ring look (not too thin) +- Place center total using `