Files
SimpleFinanceDash/.planning/research/STACK.md

365 lines
18 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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:
```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*