18 KiB
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 <WizardStepper> 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:
// 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:
/* 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, C0.04-0.06 - Accent pastel (borders, tags, icons): L
0.75-0.80, C0.10-0.12 - Text (labels, amounts): L
0.40-0.50, C0.15-0.18 (existing values are correct for this)
/* 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
/* 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:
- 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.
-- 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:
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:
-- 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 <WizardStepper> 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
# 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 — Zod 4 stable release notes, versioning strategy
- shadcn/ui blocks page — Confirmed no native stepper component; patterns available as copy-paste blocks (no npm dep)
- shadcn-ui/ui Discussion #1422 — Feature request for stepper (still open as of April 2026)
- react-hook-form/resolvers GitHub — v5.2.2 Zod v4 support
- 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 — OKLCH lightness/chroma guidance for pastel systems
package.jsonin 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