365 lines
18 KiB
Markdown
365 lines
18 KiB
Markdown
# 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:
|
||
|
||
```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 `<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
|
||
|
||
```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*
|