# Stack Research **Domain:** Personal finance dashboard — v2.0 wizard setup, auto-budget creation, sharp pastel design system **Researched:** 2026-04-02 **Confidence:** HIGH --- ## Context: What Already Exists (Do Not Re-Research) Actual installed versions from `package.json` (not the prior research document, which had aspirational versions): | Package | Actual Installed Version | Notes | |---------|-------------------------|-------| | React | ^19.2.4 | Locked | | Vite | ^8.0.0 | Locked | | TypeScript | ~5.9.3 | Locked | | Tailwind CSS | ^4.2.1 | Locked | | Recharts | **2.15.4** | Locked — NOT 3.x (prior research was wrong) | | radix-ui | ^1.4.3 | Unified post-June 2025 package | | lucide-react | ^0.577.0 | Locked | | next-themes | ^0.4.6 | Locked | | TanStack Query | ^5.90.21 | Locked | | react-router-dom | ^7.13.1 | Locked | | sonner | ^2.0.7 | Locked | | @supabase/supabase-js | ^2.99.1 | Backend is Supabase, not custom Go | | i18next + react-i18next | ^25.8.18 / ^16.5.8 | Locked | **Backend reality check:** Despite the milestone context referencing "Go 1.25 backend", the project is a Supabase-backed React SPA. There is no Go source code. All backend work happens via Supabase migrations (SQL) and the `@supabase/supabase-js` client. This is authoritative — verified from `package.json` and `supabase/migrations/`. **Form handling reality check:** Current forms (login, register, template items) use vanilla `useState` + `React.FormEvent`. No react-hook-form or zod are installed. **Design token reality check:** `--radius: 0.625rem` is applied globally and is the source of the "clunky rounded" look. Sharp redesign requires overriding this token. --- ## New Dependencies Required ### 1. react-hook-form + zod — Wizard Form Validation The wizard-style template setup requires multi-step form state that persists across steps, per-step validation before advancing, and type-safe schema enforcement. Vanilla `useState` breaks down across 4+ wizard steps with interdependent validation. **Use react-hook-form 7.72.0 + zod 4.3.6 + @hookform/resolvers 5.2.2.** | Package | Version | Purpose | Why | |---------|---------|---------|-----| | `react-hook-form` | 7.72.0 | Multi-step form state, per-field validation | Industry standard for React forms; tiny bundle (9.3kb); uncontrolled inputs avoid re-render storms across wizard steps | | `zod` | 4.3.6 | Schema validation with TypeScript inference | Zod 4 is stable (released 2025, latest 4.3.6); 14x faster than v3; subpath exports `zod/v4` enable coexistence if needed | | `@hookform/resolvers` | 5.2.2 | Bridge between zod schemas and react-hook-form | v5.2.2 supports both Zod v3 and v4 with automatic runtime detection | **Zod v4 import note:** Use `import { z } from "zod"` (root export maps to v4) unless you need v3 compatibility, then use `import { z } from "zod/v4"` explicitly. The `zodResolver` from `@hookform/resolvers/zod` handles both transparently. **Known issue:** @hookform/resolvers 5.2.1+ has reported edge-case type errors with specific Zod v4 configurations (GitHub issue #813). Use `5.2.2` (published ~February 2026) which addresses this. If type errors appear, verify the import path is `@hookform/resolvers/zod` not a sub-subpath. **Confidence: HIGH** — Versions verified via npm search results (react-hook-form 7.72.0 published April 2026; zod 4.3.6; @hookform/resolvers 5.2.2). Integration pattern is the standard shadcn/ui documented approach. --- ### 2. Wizard/Stepper UI — Custom Component (No External Library) shadcn/ui does not include a stepper or wizard component in its official core library. The discussion thread #1422 requesting it has been open for years. However, shadcn/ui's blocks library now includes stepper patterns (as of March 2026 update) built purely with shadcn/ui `Button`, `Badge`, and Tailwind — **no external npm package needed**. **Recommendation: Build a custom `` component from shadcn/ui primitives.** This approach: - Requires zero new npm packages - Matches the existing design system exactly (token-driven styling) - Is 50–80 lines of TypeScript — not complex - Avoids third-party stepper library churn (none are dominant; most are abandoned within 1-2 years) Pattern: ```tsx // src/components/shared/WizardStepper.tsx // Steps indicator: array of step objects → renders numbered circles + connecting lines // Active step gets primary color; completed steps get checkmark icon // Controlled via `currentStep: number` prop passed down from wizard page interface Step { id: string label: string } interface WizardStepperProps { steps: Step[] currentStep: number // 0-based index } ``` State management: hold `currentStep` in the wizard page component with `useState`. Each step's validation runs via `react-hook-form`'s `trigger(fieldNames)` before `setCurrentStep(n + 1)`. **Confidence: HIGH** — shadcn/ui blocks page confirms stepper patterns are available as copy-paste blocks with no external dependencies (verified March 2026). Zero external library is the correct choice. --- ### 3. shadcn/ui Components to Add The wizard needs two additional shadcn/ui components not currently installed: | Component | Install Command | Purpose | Notes | |-----------|----------------|---------|-------| | `progress` | `npx shadcn@latest add progress` | Optional step progress bar within wizard header | Built on Radix UI Progress; already themed via CSS vars. Use if linear progress indicator preferred over dot/circle steps. | | `checkbox` | `npx shadcn@latest add checkbox` | Select/deselect template items in wizard step | Built on Radix UI Checkbox; WAI-ARIA compliant. | | `scroll-area` | `npx shadcn@latest add scroll-area` | Scrollable category library panel in budget view | Built on Radix UI ScrollArea; consistent cross-browser scrollbar styling. | The existing `dialog.tsx`, `input.tsx`, `button.tsx`, `select.tsx`, `badge.tsx`, and `skeleton.tsx` are already present and sufficient for the wizard steps and inline library picker. **Confidence: HIGH** — Verified against current component list in `src/components/ui/`. --- ## Design Token Changes — Sharp Minimal Pastel System No new packages are needed. All changes are CSS variable overrides in `src/index.css`. ### Border Radius — The Core Change The v1.0 design feels "clunky and rounded" because `--radius: 0.625rem` (10px) is applied everywhere. The v2.0 "sharp minimal" direction requires: ```css /* Sharp edges — change ONE token, affects all shadcn/ui components */ --radius: 0.125rem; /* 2px — sharp but not perfectly square */ /* Alternative: 0rem for fully square (more aggressive) */ ``` This single change propagates through every shadcn/ui component because they all use `rounded-[var(--radius)]` or similar. No individual component changes needed. ### Pastel Color Recalibration The current category colors use L~0.55 (text-weight, too dark for fills and backgrounds). "Clear pastels" in a sharp minimal design system need a two-tier system: - **Surface pastel** (card backgrounds, highlights): L~0.93-0.96, C~0.04-0.06 - **Accent pastel** (borders, tags, icons): L~0.75-0.80, C~0.10-0.12 - **Text** (labels, amounts): L~0.40-0.50, C~0.15-0.18 (existing values are correct for this) ```css /* New: pastel surface colors for wizard step indicators, category cards */ --color-income-surface: oklch(0.95 0.04 155); --color-bill-surface: oklch(0.95 0.04 25); --color-variable-expense-surface: oklch(0.95 0.04 50); --color-debt-surface: oklch(0.95 0.05 355); --color-saving-surface: oklch(0.95 0.04 220); --color-investment-surface: oklch(0.95 0.04 285); /* New: accent pastel (for badges, step indicators, borders) */ --color-income-accent: oklch(0.80 0.10 155); --color-bill-accent: oklch(0.80 0.10 25); --color-variable-expense-accent: oklch(0.80 0.10 50); --color-debt-accent: oklch(0.80 0.11 355); --color-saving-accent: oklch(0.80 0.10 220); --color-investment-accent: oklch(0.80 0.10 285); ``` The existing text-weight category colors (`--color-income`, etc.) remain unchanged — they're already correct for text/icon usage and pass WCAG 4.5:1. ### Minimal Layout Tokens ```css /* Reduce visual weight for card borders — sharp design reads better with subtle borders */ --color-border: oklch(0.90 0.008 260); /* slightly lighter than current 0.88 */ /* Tighter card padding via CSS custom property (used in PageShell / StatCard) */ /* No token needed — use Tailwind p-4 consistently instead of p-6 */ ``` **Confidence: HIGH** — Based on OKLCH perceptual uniformity properties; the lightness/chroma values follow the Evil Martians OKLCH guide for pastel palette design. --- ## Backend Changes — Supabase (Auto-Budget Creation + Template Seeding) ### Auto-Budget Creation The feature "auto-create monthly budgets from template on first visit" requires: 1. **A Supabase RPC function** (PostgreSQL function called via `.rpc()`) to atomically create a budget + copy template items in a single round-trip. Application-layer logic (JavaScript calling multiple inserts) is fragile across network failures. ```sql -- New migration: create_budget_from_template(user_id, month_date) -- Returns the new budget_id -- Idempotent: if budget for that month already exists, returns existing id create or replace function create_budget_from_template( p_user_id uuid, p_month date -- first day of the target month ) returns uuid language plpgsql security definer -- needed to bypass RLS for the INSERT as $$ declare v_template_id uuid; v_budget_id uuid; begin -- Get user's template select id into v_template_id from templates where user_id = p_user_id limit 1; if v_template_id is null then return null; end if; -- Idempotency: return existing budget for this month select id into v_budget_id from budgets where user_id = p_user_id and start_date = date_trunc('month', p_month)::date limit 1; if v_budget_id is not null then return v_budget_id; end if; -- Create new budget insert into budgets (user_id, start_date, end_date, currency) values ( p_user_id, date_trunc('month', p_month)::date, (date_trunc('month', p_month) + interval '1 month' - interval '1 day')::date, (select currency from profiles where id = p_user_id limit 1) ) returning id into v_budget_id; -- Copy template items to budget items insert into budget_items (budget_id, category_id, budgeted_amount, item_tier) select v_budget_id, ti.category_id, ti.budgeted_amount, ti.item_tier from template_items ti where ti.template_id = v_template_id; return v_budget_id; end; $$; ``` Frontend call: ```typescript const { data: budgetId } = await supabase.rpc('create_budget_from_template', { p_user_id: user.id, p_month: `${year}-${month}-01` }) ``` **Why RPC not application layer:** A single RPC is atomic (no partial state if network drops mid-sequence), runs in 1 round-trip instead of 3-4, and the idempotency guard prevents duplicate budgets from double-calls (React StrictMode double-invocation, fast navigation, etc.). ### Template Seeding (Wizard First-Run) The wizard "pre-filled common items" require a static list seeded client-side — no backend change needed. The wizard presents checkboxes for ~15 common items (Rent, Salary, Car Insurance, Groceries, etc.) with pre-populated amounts. On wizard completion, one batch `insert` creates the template + template_items. The existing `templates` and `template_items` tables support this without schema changes. **Categories pre-seeding:** Categories (income, bill, variable_expense, etc.) are per-user, not system-wide. The wizard must also insert default categories before template items can reference them. Order: insert default categories → insert template → insert template_items. This can also be an RPC (`setup_user_template`) or application-layer batch — the wizard runs once, so network failure risk is acceptable at client-side orchestration. ### Category Library (Inline Add-from-Library) The `quick_add_items` table stores name + icon only — no amount or category_type. For the inline category library in budget view (replacing the QuickAdd page), the library needs category_type to know which budget section to add to. **Schema addition needed:** ```sql -- New migration: add category_type to quick_add_items alter table quick_add_items add column category_type category_type, -- existing enum add column default_amount numeric(12,2) default 0; ``` This enables the library to show items grouped by type and pre-fill amounts when adding to a budget. **Confidence: MEDIUM** — RPC pattern is well-established in Supabase. Schema addition is straightforward. The exact RPC SQL above is a starting point and may need adjustment based on profiles table structure (currency field). Flag for verification in planning phase. --- ## What NOT to Add | Avoid | Why | Use Instead | |-------|-----|-------------| | External stepper library (react-use-wizard, react-step-wizard, stepperize) | None is dominant; all add bundle weight for what is 50 lines of custom code; APIs change | Custom `` using shadcn/ui Button + Badge + Tailwind | | Zod v3 (avoid if installing fresh) | v4 is stable and 14x faster; no reason to use v3 for new code | `zod@4.3.6` | | Framer Motion for wizard step transitions | 28kb gzip; CSS `transition-opacity duration-200` is sufficient for step fade | Tailwind transition utilities | | Recharts upgrade to 3.x in this milestone | 2.15.4 is working; v3 migration guide lists breaking changes that would require updating all existing charts; out of scope | Stay on 2.15.4 for v2.0 milestone; plan upgrade separately | | CSS-in-JS for new design tokens | Tailwind v4 `@theme inline` in `index.css` already handles all theming | Extend `index.css` `@theme inline` block | | Zustand for wizard state | TanStack Query + useState is already the state pattern in this app; wizard state is local and short-lived | `useState` in wizard page component | | Supabase Edge Functions for auto-budget | PostgreSQL function (RPC) runs in the same DB transaction; simpler, no Deno runtime | Supabase RPC (pg function) | --- ## Alternatives Considered | Recommended | Alternative | When to Use Alternative | |-------------|-------------|-------------------------| | Custom WizardStepper | `react-use-wizard` (npm) | If wizard logic were complex (branching paths, async steps with external dependencies) — this wizard is linear with simple validation | | Zod 4 | Valibot | If bundle size were critical (Valibot is ~1kb vs Zod's ~14kb) — not a concern here | | Supabase RPC for auto-budget | Application-layer multi-insert | Acceptable if you tolerate partial failure risk and handle retry logic — RPC is cleaner | | Two-tier OKLCH pastel tokens | Single token set | If the design only needed chart colors — the wizard and library UI need surface pastels that the existing single-tier system doesn't provide | --- ## Installation ```bash # Wizard form validation (new packages) bun add react-hook-form@7.72.0 zod@4.3.6 @hookform/resolvers@5.2.2 # New shadcn/ui components npx shadcn@latest add progress npx shadcn@latest add checkbox npx shadcn@latest add scroll-area ``` No other npm/bun installs needed. All other new functionality (wizard stepper UI, design token changes, Supabase RPC, schema migration) is implemented via local code and SQL. --- ## Version Compatibility | Package | Version | Compatible With | Notes | |---------|---------|-----------------|-------| | react-hook-form | 7.72.0 | React 19 | Fully compatible; hooks-based, no legacy API | | zod | 4.3.6 | TypeScript ~5.9.3 | Requires TypeScript 4.5+; project is on 5.9.3 — fine | | @hookform/resolvers | 5.2.2 | react-hook-form 7.x + zod 4.x | Automatic v3/v4 Zod detection | | Recharts | 2.15.4 | React 19 | Stays on v2; DO NOT upgrade in this milestone | | Tailwind CSS | 4.2.1 | `@theme inline` | Sharp radius via `--radius: 0.125rem` in `@theme inline` block | | radix-ui | 1.4.3 | shadcn new-york style | New shadcn add commands generate `radix-ui` imports (unified package), not `@radix-ui/*` | --- ## Integration Points ### Wizard → Template → Budget Flow ``` WizardPage (new route: /setup) └─ react-hook-form (FormProvider wrapping all steps) └─ WizardStepper component (step indicator) └─ Step 1: Pick categories (checkbox + default amounts) └─ Step 2: Adjust fixed items (input fields, pre-filled) └─ Step 3: Adjust variable items └─ Step 4: Review + confirm └─ onSubmit → batch insert categories + template + template_items └─ redirect to /dashboard DashboardPage (existing) └─ on mount: check if budget exists for current month └─ if not: call supabase.rpc('create_budget_from_template', { month }) └─ render budget data (existing pattern, improved display) BudgetDetailPage (existing, enhanced) └─ inline category library panel (uses scroll-area + checkbox/button) └─ add one-off items directly from library ``` ### Design Token Change Impact The `--radius` change in `index.css` requires no component-level changes — all shadcn/ui components use the token automatically. The new surface/accent OKLCH tokens need to be applied explicitly in new components (wizard steps, category library cards); existing components are unaffected. --- ## Sources - npm search results (April 2026) — react-hook-form 7.72.0, zod 4.3.6, @hookform/resolvers 5.2.2 - [zod.dev/v4](https://zod.dev/v4) — Zod 4 stable release notes, versioning strategy - [shadcn/ui blocks page](https://ui.shadcn.com/blocks) — Confirmed no native stepper component; patterns available as copy-paste blocks (no npm dep) - [shadcn-ui/ui Discussion #1422](https://github.com/shadcn-ui/ui/discussions/1422) — Feature request for stepper (still open as of April 2026) - [react-hook-form/resolvers GitHub](https://github.com/react-hook-form/resolvers/releases) — v5.2.2 Zod v4 support - [github.com/recharts/recharts/wiki/3.0-migration-guide](https://github.com/recharts/recharts/wiki/3.0-migration-guide) — v3 breaking changes confirming upgrade is out of scope for this milestone - [evilmartians.com — Better dynamic themes with OKLCH](https://evilmartians.com/chronicles/better-dynamic-themes-in-tailwind-with-oklch-color-magic) — OKLCH lightness/chroma guidance for pastel systems - `package.json` in project root — authoritative source for actual installed versions (Recharts 2.15.4, not 3.x as prior research stated) - `supabase/migrations/` — authoritative source confirming Supabase backend, existing schema --- *Stack research for: SimpleFinanceDash v2.0 — Wizard Setup, Auto-Budget, Sharp Pastel Design* *Researched: 2026-04-02*