Files
SimpleFinanceDash/.planning/research/STACK.md

18 KiB
Raw Blame History

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 5080 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): L0.93-0.96, C0.04-0.06
  • Accent pastel (borders, tags, icons): L0.75-0.80, C0.10-0.12
  • Text (labels, amounts): L0.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:

  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.
-- 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


Stack research for: SimpleFinanceDash v2.0 — Wizard Setup, Auto-Budget, Sharp Pastel Design Researched: 2026-04-02