546 lines
28 KiB
Markdown
546 lines
28 KiB
Markdown
# Architecture Research
|
||
|
||
**Domain:** Personal budget dashboard — v2.0 UX simplification with wizard setup, auto-budget creation, inline add-from-library, design system rework
|
||
**Researched:** 2026-04-02
|
||
**Confidence:** HIGH (based on full codebase inspection; all claims verified against source files)
|
||
|
||
---
|
||
|
||
## Existing Architecture Baseline
|
||
|
||
This is a subsequent milestone. The section below describes what EXISTS today, then each sub-section documents exactly what changes and what stays the same.
|
||
|
||
### Current System Layout
|
||
|
||
```
|
||
┌─────────────────────────────────────────────────────────────────────┐
|
||
│ React 19 SPA (Vite + TypeScript) │
|
||
│ │
|
||
│ Auth Layer App Layer (ProtectedRoute + AppLayout) │
|
||
│ ┌───────────────┐ ┌─────────────────────────────────────────┐ │
|
||
│ │ LoginPage │ │ SidebarProvider > Sidebar > SidebarInset│ │
|
||
│ │ RegisterPage │ │ 6 nav items → 6 protected page routes │ │
|
||
│ └───────────────┘ └─────────────────────────────────────────┘ │
|
||
│ │
|
||
│ Pages (each uses PageShell + direct hook calls): │
|
||
│ DashboardPage CategoriesPage TemplatePage BudgetListPage │
|
||
│ BudgetDetailPage QuickAddPage SettingsPage │
|
||
│ │
|
||
│ Hooks (TanStack Query v5, direct supabase-js client): │
|
||
│ useAuth useCategories useTemplate useBudgets useQuickAdd │
|
||
│ useMonthParam useBudgetDetail │
|
||
│ │
|
||
│ Design layer: │
|
||
│ index.css @theme inline → OKLCH tokens → Tailwind 4 utility classes│
|
||
│ lib/palette.ts → categoryColors (CSS var references only) │
|
||
│ shadcn/ui (radix-ui primitives + generated component files) │
|
||
└─────────────────────────┬───────────────────────────────────────────┘
|
||
│ @supabase/supabase-js v2 (REST + RLS)
|
||
┌─────────────────────────▼───────────────────────────────────────────┐
|
||
│ Supabase (PostgreSQL 16 + Auth + Row Level Security) │
|
||
│ Tables: profiles, categories, templates, template_items, │
|
||
│ budgets, budget_items, quick_add_items │
|
||
└─────────────────────────────────────────────────────────────────────┘
|
||
```
|
||
|
||
### Key Existing Patterns (Carry Forward Unchanged)
|
||
|
||
| Pattern | Location | Keep? |
|
||
|---------|----------|-------|
|
||
| PageShell header wrapper | `components/shared/PageShell.tsx` | YES |
|
||
| DashboardContent inner component keyed by budgetId | `DashboardPage.tsx` | YES — key prop pattern prevents stale state on month change |
|
||
| useMonthParam URL search param | `hooks/useMonthParam.ts` | YES |
|
||
| getOrCreateTemplate (auto-creates template row) | `hooks/useTemplate.ts` | YES |
|
||
| generateFromTemplate mutation | `hooks/useBudgets.ts` | YES — v2.0 triggers it automatically |
|
||
| Two-tier OKLCH color tokens (text ~0.55L, fill ~0.68L) | `index.css` | YES — values tuned, names unchanged |
|
||
| categoryColors CSS var references | `lib/palette.ts` | YES — names unchanged |
|
||
| Direction-aware diff (income under = bad, spending over = bad) | `BudgetDetailPage.tsx`, `CategorySection.tsx` | YES |
|
||
| TanStack Query cache invalidation per resource key | All mutation hooks | YES |
|
||
|
||
---
|
||
|
||
## v2.0 Architecture Changes
|
||
|
||
### 1. First-Run Detection and Wizard Routing
|
||
|
||
**Problem:** New users land on Dashboard with empty categories, no template, no budget. The blank state offers no guidance.
|
||
|
||
**Solution:** A `useFirstRunState` hook detects first-run status. A `WizardRoute` guard in `App.tsx` redirects first-run users to `/setup` (a dedicated wizard page) before they can reach any other protected route.
|
||
|
||
**New hook — `src/hooks/useFirstRunState.ts`:**
|
||
|
||
```typescript
|
||
// Returns { needsSetup: boolean, loading: boolean }
|
||
// needsSetup = true when: categories.length === 0 OR template_items.length === 0
|
||
// Uses existing useCategories() and useTemplate() — no new DB queries
|
||
export function useFirstRunState(): { needsSetup: boolean; loading: boolean }
|
||
```
|
||
|
||
**Route change — `src/App.tsx`:**
|
||
|
||
```
|
||
// Add /setup route
|
||
// Add WizardRoute guard that wraps the existing ProtectedRoute subtree:
|
||
// if needsSetup && path !== "/setup" → redirect to /setup
|
||
// if !needsSetup && path === "/setup" → redirect to /
|
||
|
||
<Route path="/setup" element={<ProtectedRoute><SetupWizardPage /></ProtectedRoute>} />
|
||
```
|
||
|
||
**Wizard state — local `useState` only in `SetupWizardPage`:**
|
||
|
||
```typescript
|
||
type WizardState = {
|
||
step: 1 | 2 | 3
|
||
selectedPresets: string[] // preset IDs chosen in step 1
|
||
amounts: Record<string, number> // presetId → budgeted amount (step 2)
|
||
}
|
||
```
|
||
|
||
Wizard state is ephemeral. It lives in the page component and is discarded on unmount. No context, no global store.
|
||
|
||
**Completion flow:** `handleFinish()` in `SetupWizardPage` runs:
|
||
1. `useCategories().create` × N (create one category row per selected preset)
|
||
2. `useTemplate().createItem` × N (create template items, each linked to the new categories)
|
||
3. `navigate("/")` — DashboardPage loads, `useFirstRunState` returns `needsSetup=false`
|
||
|
||
**Preset data — `src/data/presets.ts`:** Static array of common budget items (rent, salary, groceries, car insurance, etc.) with suggested amounts and category types. No DB table needed — this is compile-time data.
|
||
|
||
**No new DB migration.** Wizard writes to existing `categories` and `template_items` tables.
|
||
|
||
**New files:**
|
||
- `src/hooks/useFirstRunState.ts`
|
||
- `src/pages/SetupWizardPage.tsx`
|
||
- `src/components/wizard/WizardStep.tsx`
|
||
- `src/components/wizard/CategoryDefaults.tsx`
|
||
- `src/components/wizard/TemplateDefaults.tsx`
|
||
- `src/data/presets.ts`
|
||
|
||
**Modified files:**
|
||
- `src/App.tsx` — add `/setup` route + WizardRoute guard
|
||
|
||
---
|
||
|
||
### 2. Auto-Budget Creation on Month Visit
|
||
|
||
**Problem:** Visiting the dashboard for a new month shows an empty state with two manual buttons ("Create Budget", "Generate from Template"). This friction contradicts the "just works" goal.
|
||
|
||
**Solution:** Auto-trigger `generateFromTemplate` inside a `useEffect` in `DashboardPage` when no budget exists for the current month and the user's template has items.
|
||
|
||
**Integration point — `src/pages/DashboardPage.tsx`:**
|
||
|
||
Add `useTemplate()` call alongside the existing `useBudgets()` call. Add a `useEffect` with these guards:
|
||
|
||
```typescript
|
||
const { items: templateItems } = useTemplate()
|
||
const hasTemplateItems = templateItems.length > 0
|
||
const isCurrentMonth = month === currentMonth // currentMonth from useMonthParam baseline
|
||
const attempted = useRef(false)
|
||
|
||
useEffect(() => {
|
||
if (
|
||
!loading &&
|
||
!currentBudget &&
|
||
hasTemplateItems &&
|
||
isCurrentMonth &&
|
||
!attempted.current &&
|
||
!generateFromTemplate.isPending
|
||
) {
|
||
attempted.current = true
|
||
generateFromTemplate.mutate({ month: parsedMonth, year: parsedYear, currency })
|
||
}
|
||
}, [loading, currentBudget, hasTemplateItems, isCurrentMonth])
|
||
```
|
||
|
||
**Currency source:** The `generateFromTemplate` mutation currently accepts `currency` as a param. Pull the user's preferred currency from the profile via a `useProfile` hook or extend `useAuth` to expose it. If `useProfile` is not yet implemented, fall back to reading from the most recent budget's `currency` field, or default to the settings page value.
|
||
|
||
**No new API endpoint.** The `generateFromTemplate` mutation in `useBudgets.ts` already handles the full create + seed flow server-side. This is a pure frontend trigger change.
|
||
|
||
**Edge cases handled by the guards:**
|
||
- Template has 0 items → `hasTemplateItems=false` → skip auto-create, show prompt to configure template
|
||
- Budget already exists → `!currentBudget=false` → skip
|
||
- Past/future month navigation → `isCurrentMonth=false` → skip (only auto-create for today's month)
|
||
- Multiple renders before mutation completes → `attempted` ref prevents double-fire
|
||
|
||
**Modified files:**
|
||
- `src/pages/DashboardPage.tsx` — add useTemplate(), useEffect trigger, currency source
|
||
|
||
---
|
||
|
||
### 3. Inline Add-from-Category-Library (Replaces QuickAdd Page)
|
||
|
||
**Problem:** QuickAdd is a separate nav page (`/quick-add`), a separate table (`quick_add_items`), and a Popover on the Dashboard. Three disconnected surfaces for one concept. The mental model is unclear.
|
||
|
||
**Solution:** Replace the QuickAdd Popover with an `AddOneOffSheet` component (a shadcn Sheet/side panel) available directly on BudgetDetailPage and Dashboard. It shows the user's category list grouped by type. The user picks a category, enters an amount, and a `one_off` budget item is created. The `quick_add_items` table remains intact but is removed from the primary workflow.
|
||
|
||
**New component — `src/components/budget/AddOneOffSheet.tsx`:**
|
||
|
||
```typescript
|
||
interface AddOneOffSheetProps {
|
||
budgetId: string
|
||
open: boolean
|
||
onOpenChange: (open: boolean) => void
|
||
}
|
||
// Uses: useCategories() + useBudgets().createItem
|
||
// On save: createItem.mutate({ budgetId, category_id, budgeted_amount, actual_amount, notes, item_tier: "one_off" })
|
||
```
|
||
|
||
This replaces `QuickAddPicker.tsx` at both call sites. The existing `useBudgets().createItem` mutation is unchanged — it already inserts `item_tier: "one_off"` budget items.
|
||
|
||
**QuickAdd page fate:** Remove from `AppLayout.tsx` nav items array. Keep the route and page file in place (backwards compat, existing data). Do NOT drop the `quick_add_items` table or `useQuickAdd` hook.
|
||
|
||
**Modified files:**
|
||
- `src/components/AppLayout.tsx` — remove QuickAdd from `navItems` array
|
||
- `src/pages/DashboardPage.tsx` — replace `<QuickAddPicker>` with `<AddOneOffSheet>`
|
||
- `src/pages/BudgetDetailPage.tsx` — replace `<QuickAddPicker>` with `<AddOneOffSheet>`
|
||
|
||
**New files:**
|
||
- `src/components/budget/AddOneOffSheet.tsx`
|
||
|
||
**No DB migration needed.** `one_off` budget items already exist in `budget_items` with `item_tier = 'one_off'`.
|
||
|
||
---
|
||
|
||
### 4. Dashboard Simplification
|
||
|
||
**Problem:** The current dashboard renders three chart types (donut + two bar charts) in a 3-column grid plus collapsible sections. This is dense and the charts don't clearly reflect user-entered data. v2.0 goal: "this month's budget at a glance."
|
||
|
||
**Solution:** Remove the 3-column chart grid from `DashboardContent`. Keep `SummaryStrip` (3 KPI cards) and `CollapsibleSections`. The chart components stay in the codebase — they are correct — but are removed from the Dashboard layout.
|
||
|
||
**Modified files:**
|
||
- `src/pages/DashboardPage.tsx` — remove chart grid `<div>` and the three chart imports from `DashboardContent`
|
||
|
||
**Dashboard data correctness:** The aggregation logic (`totalIncome`, `totalExpenses`, `budgetedIncome`, `budgetedExpenses`) is already correct in `DashboardContent`. The perceived "data not reflecting input" issue is caused by the auto-create flow not existing yet — once a budget exists and items are populated, the existing calculations are accurate. No aggregation logic changes needed.
|
||
|
||
**Empty state after removing charts:** The removed chart area frees vertical space. Collapsible sections become the primary view. The `SummaryStrip` at the top provides the quick-glance summary. Consider adding a single "at a glance" progress indicator to `SummaryStrip` to compensate for the removed chart density.
|
||
|
||
---
|
||
|
||
### 5. Design System Token Rework
|
||
|
||
**Problem:** `--radius: 0.625rem` produces rounded corners everywhere. The palette has high chroma values that read as saturated rather than pastel.
|
||
|
||
**Solution:** Lower the radius token and tune OKLCH lightness/chroma in `index.css`. All shadcn/ui components reference `--radius` via `rounded-[--radius]` — a single value change propagates everywhere.
|
||
|
||
**The only changed file: `src/index.css`**
|
||
|
||
Key changes:
|
||
```css
|
||
/* Sharp edges */
|
||
--radius: 0.125rem; /* was 0.625rem */
|
||
|
||
/* Background: warmer, closer to pure white */
|
||
--color-background: oklch(0.99 0.003 260); /* was 0.98 0.005 260 */
|
||
|
||
/* Softer border */
|
||
--color-border: oklch(0.91 0.008 260); /* was 0.88 0.01 260 */
|
||
|
||
/* Category pastels: increase lightness slightly, keep chroma */
|
||
/* e.g. --color-income-fill: oklch(0.72 0.14 155) → 0.78 0.12 155 */
|
||
```
|
||
|
||
**What does NOT change:**
|
||
- Token names (e.g., `--color-income`, `--color-bill`) — `palette.ts` references these by name
|
||
- Two-tier pattern (text tokens at ~0.55L, fill tokens at ~0.68–0.78L)
|
||
- Category color identities (income=green, bill=orange-red, etc.)
|
||
- The `@theme inline` structure
|
||
|
||
**shadcn component customization:** Zero component-file changes required. Radius change propagates automatically. Color changes propagate via CSS var resolution.
|
||
|
||
---
|
||
|
||
## Updated System Layout (v2.0)
|
||
|
||
```
|
||
┌─────────────────────────────────────────────────────────────────────┐
|
||
│ React 19 SPA │
|
||
│ │
|
||
│ Auth Layer App Layer │
|
||
│ ┌───────────────┐ ┌─────────────────────────────────────────┐ │
|
||
│ │ LoginPage │ │ WizardRoute (NEW) │ │
|
||
│ │ RegisterPage │ │ └─ ProtectedRoute + AppLayout (MOD) │ │
|
||
│ └───────────────┘ │ Sidebar (5 nav items, -QuickAdd) │ │
|
||
│ └─────────────────────────────────────────┘ │
|
||
│ │
|
||
│ New route /setup: │
|
||
│ SetupWizardPage (NEW) → 3 steps → writes categories + template │
|
||
│ │
|
||
│ Modified pages: │
|
||
│ DashboardPage (MOD: auto-create, -charts, AddOneOffSheet) │
|
||
│ BudgetDetailPage (MOD: AddOneOffSheet replaces QuickAddPicker) │
|
||
│ │
|
||
│ Unchanged pages: │
|
||
│ CategoriesPage TemplatePage BudgetListPage SettingsPage │
|
||
│ QuickAddPage (hidden from nav, route kept) │
|
||
│ │
|
||
│ New components: │
|
||
│ wizard/WizardStep wizard/CategoryDefaults wizard/TemplateDefaults│
|
||
│ budget/AddOneOffSheet │
|
||
│ │
|
||
│ New hooks: │
|
||
│ useFirstRunState │
|
||
│ │
|
||
│ New data: │
|
||
│ src/data/presets.ts (static, no DB) │
|
||
└─────────────────────────┬───────────────────────────────────────────┘
|
||
│ @supabase/supabase-js v2 (unchanged)
|
||
┌─────────────────────────▼───────────────────────────────────────────┐
|
||
│ Supabase (unchanged schema, no new migrations) │
|
||
│ All new features write to existing tables │
|
||
└─────────────────────────────────────────────────────────────────────┘
|
||
```
|
||
|
||
---
|
||
|
||
## Component Boundary Map (v2.0)
|
||
|
||
```
|
||
App.tsx
|
||
├── /login → LoginPage
|
||
├── /register → RegisterPage
|
||
├── /setup → ProtectedRoute > SetupWizardPage (NEW)
|
||
│ ├── WizardStep (NEW)
|
||
│ ├── step 1: CategoryDefaults (NEW) — checkboxes from presets.ts
|
||
│ └── step 2: TemplateDefaults (NEW) — amount inputs per selected preset
|
||
└── /* → WizardRoute > ProtectedRoute > AppLayout (MODIFIED: -QuickAdd nav)
|
||
├── / → DashboardPage (MODIFIED)
|
||
│ ├── MonthNavigator
|
||
│ ├── DashboardContent (MODIFIED: no chart grid)
|
||
│ │ ├── SummaryStrip (unchanged)
|
||
│ │ ├── CollapsibleSections (unchanged)
|
||
│ │ └── AddOneOffSheet (NEW, replaces QuickAddPicker)
|
||
├── /categories → CategoriesPage (unchanged)
|
||
├── /template → TemplatePage (unchanged)
|
||
├── /budgets → BudgetListPage (unchanged)
|
||
├── /budgets/:id → BudgetDetailPage (MODIFIED)
|
||
│ └── AddOneOffSheet (NEW, replaces QuickAddPicker)
|
||
├── /quick-add → QuickAddPage (hidden from nav, route kept)
|
||
└── /settings → SettingsPage (unchanged)
|
||
```
|
||
|
||
---
|
||
|
||
## Data Flow Changes
|
||
|
||
### First-Run + Wizard Completion Flow
|
||
|
||
```
|
||
New user lands on any protected route
|
||
↓
|
||
WizardRoute: useFirstRunState() → needsSetup=true
|
||
↓
|
||
redirect /setup
|
||
↓
|
||
SetupWizardPage renders (step 1)
|
||
User checks preset categories (Rent, Salary, Groceries, ...)
|
||
↓ [Next]
|
||
step 2: User adjusts amounts per selected category
|
||
↓ [Finish] → handleFinish()
|
||
↓
|
||
useCategories().create × N → INSERT categories (N rows)
|
||
↓ await
|
||
useTemplate().createItem × N → INSERT template_items (N rows)
|
||
↓ await
|
||
navigate("/")
|
||
↓
|
||
DashboardPage: useFirstRunState() → needsSetup=false
|
||
Auto-create effect fires (see below)
|
||
```
|
||
|
||
### Auto-Budget Creation Flow
|
||
|
||
```
|
||
DashboardPage mounts (month = "2026-04", current month)
|
||
↓
|
||
useBudgets() → budgets list → currentBudget = undefined
|
||
useTemplate() → items → hasTemplateItems = true
|
||
↓
|
||
useEffect fires:
|
||
!loading && !currentBudget && hasTemplateItems && isCurrentMonth && !attempted
|
||
↓
|
||
generateFromTemplate.mutate({ month: 4, year: 2026, currency: "EUR" })
|
||
↓
|
||
Supabase:
|
||
1. INSERT budgets (start_date=2026-04-01, end_date=2026-04-30)
|
||
2. SELECT template_items WHERE template_id = user's template
|
||
3. INSERT budget_items × N (actual_amount=0 per item)
|
||
↓
|
||
onSuccess: invalidate ["budgets"], ["budgets", id], ["budgets", id, "items"]
|
||
↓
|
||
DashboardPage rerenders: currentBudget = new budget
|
||
DashboardContent mounts (keyed by budgetId)
|
||
SummaryStrip + CollapsibleSections render with template-populated data
|
||
```
|
||
|
||
### Inline Add-One-Off Flow
|
||
|
||
```
|
||
DashboardPage or BudgetDetailPage
|
||
"Add one-off" button → AddOneOffSheet opens (Sheet side panel)
|
||
↓
|
||
useCategories() provides category list (grouped by type)
|
||
User picks category, enters amount, optional notes
|
||
↓
|
||
[Add] → useBudgets().createItem.mutate({
|
||
budgetId,
|
||
category_id,
|
||
budgeted_amount: amount,
|
||
actual_amount: amount, // one-off: budget = actual at time of entry
|
||
notes,
|
||
item_tier: "one_off"
|
||
})
|
||
↓
|
||
onSuccess: invalidate ["budgets", budgetId, "items"]
|
||
Sheet closes → item appears in appropriate CollapsibleSection
|
||
```
|
||
|
||
---
|
||
|
||
## New vs Modified File Summary
|
||
|
||
### New Files
|
||
|
||
| File | Purpose |
|
||
|------|---------|
|
||
| `src/hooks/useFirstRunState.ts` | Detect first-run: categories=0 OR template_items=0 |
|
||
| `src/data/presets.ts` | Static preset category list with suggested amounts |
|
||
| `src/pages/SetupWizardPage.tsx` | Multi-step first-run wizard (local state, 3 steps) |
|
||
| `src/components/wizard/WizardStep.tsx` | Step wrapper with step indicator / progress |
|
||
| `src/components/wizard/CategoryDefaults.tsx` | Preset category checkbox picker (step 1) |
|
||
| `src/components/wizard/TemplateDefaults.tsx` | Amount inputs per selected preset (step 2) |
|
||
| `src/components/budget/AddOneOffSheet.tsx` | Sheet-based one-off item adder (replaces QuickAddPicker) |
|
||
|
||
### Modified Files
|
||
|
||
| File | What Changes |
|
||
|------|-------------|
|
||
| `src/App.tsx` | Add `/setup` route; add WizardRoute guard |
|
||
| `src/components/AppLayout.tsx` | Remove QuickAdd from `navItems` array |
|
||
| `src/pages/DashboardPage.tsx` | Add useTemplate(); add auto-create useEffect; remove chart grid; swap AddOneOffSheet for QuickAddPicker |
|
||
| `src/pages/BudgetDetailPage.tsx` | Swap AddOneOffSheet for QuickAddPicker |
|
||
| `src/index.css` | Lower `--radius`; tune OKLCH pastel token values |
|
||
| `src/i18n/en.json` | Add wizard i18n keys; remove/rename quick-add nav key |
|
||
| `src/i18n/de.json` | Same additions as en.json (bilingual requirement) |
|
||
|
||
### Unchanged Files (confirmed)
|
||
|
||
`useBudgets.ts`, `useTemplate.ts`, `useCategories.ts`, `useQuickAdd.ts`, `useAuth.ts`, `useMonthParam.ts`, `lib/types.ts`, `lib/palette.ts`, `lib/format.ts`, `lib/supabase.ts` — all hooks and library files are read-only for this milestone.
|
||
|
||
---
|
||
|
||
## Build Order (Dependency-Aware)
|
||
|
||
Phase dependencies must be respected. Build bottom-up.
|
||
|
||
**Phase 1: Design Tokens (no deps)**
|
||
- `src/index.css` — lower `--radius`, refine OKLCH pastel values
|
||
- Verify all 9 existing pages render correctly; no logic changes
|
||
- Fast and reversible — do this first to establish visual baseline
|
||
|
||
**Phase 2: Preset Data + First-Run Hook (no deps)**
|
||
- `src/data/presets.ts` — static data, no imports needed
|
||
- `src/hooks/useFirstRunState.ts` — depends on existing `useCategories` + `useTemplate`; no UI yet
|
||
|
||
**Phase 3: Setup Wizard (depends on Phase 2)**
|
||
- `src/components/wizard/WizardStep.tsx` — pure presentational
|
||
- `src/components/wizard/CategoryDefaults.tsx` — depends on presets.ts
|
||
- `src/components/wizard/TemplateDefaults.tsx` — depends on presets.ts
|
||
- `src/pages/SetupWizardPage.tsx` — assembles wizard components + useFirstRunState + mutation hooks
|
||
- `src/App.tsx` — wire `/setup` route + WizardRoute guard
|
||
|
||
**Phase 4: Auto-Budget Creation (depends on Phase 3 — template must be populatable)**
|
||
- `src/pages/DashboardPage.tsx` — add `useTemplate()` import + auto-create `useEffect`
|
||
- Test: wizard → dashboard → budget auto-creates → SummaryStrip shows template data
|
||
|
||
**Phase 5: Inline Add-One-Off + Dashboard Simplification (depends on Phase 4)**
|
||
- `src/components/budget/AddOneOffSheet.tsx` — depends on `useCategories` + `useBudgets` (both existing)
|
||
- `src/pages/DashboardPage.tsx` — remove chart grid; wire AddOneOffSheet
|
||
- `src/pages/BudgetDetailPage.tsx` — wire AddOneOffSheet
|
||
- `src/components/AppLayout.tsx` — remove QuickAdd nav item
|
||
|
||
---
|
||
|
||
## Anti-Patterns to Avoid
|
||
|
||
### Anti-Pattern 1: Global State for Wizard
|
||
|
||
**What people do:** Store wizard state in a React context, Zustand store, or TanStack Query mutation cache.
|
||
**Why it's wrong:** Wizard runs once, on first login. Global state persists unnecessarily and creates cleanup complexity.
|
||
**Do this instead:** Local `useState` in `SetupWizardPage`. When the page unmounts (after `navigate("/")`), state is automatically cleaned up.
|
||
|
||
### Anti-Pattern 2: Auto-Create Without Template Guard
|
||
|
||
**What people do:** Trigger `generateFromTemplate` whenever `!currentBudget`, regardless of template state.
|
||
**Why it's wrong:** If a user skips the wizard or has an empty template, an empty budget gets silently created. This is worse than the empty state — it looks like a bug, not a fresh start.
|
||
**Do this instead:** Guard with `hasTemplateItems && isCurrentMonth`. Show a helpful CTA to configure the template if template is empty.
|
||
|
||
### Anti-Pattern 3: Dropping the quick_add_items Table
|
||
|
||
**What people do:** Add a migration to drop `quick_add_items` when the QuickAdd nav item is removed.
|
||
**Why it's wrong:** Existing users have data there. The DB migration is irreversible. The table costs nothing to keep.
|
||
**Do this instead:** Remove the nav item and surface only. Leave the table, `useQuickAdd` hook, and `/quick-add` route in place. Schedule a cleanup migration in a future milestone after confirming no users rely on it.
|
||
|
||
### Anti-Pattern 4: Renaming Design Token CSS Variables
|
||
|
||
**What people do:** Rename tokens during the design rework (e.g., `--color-income` → `--category-color-income`) to make the naming more semantic.
|
||
**Why it's wrong:** `palette.ts` references token names as strings: `var(--color-income)`. All chart components use `var(--color-income-fill)`. Renaming causes silent CSS failures — no TypeScript error, no build error, just missing colors.
|
||
**Do this instead:** Change only the VALUES of existing tokens. Add new token names only for net-new concepts.
|
||
|
||
### Anti-Pattern 5: Wizard as Modal Overlay
|
||
|
||
**What people do:** Show the wizard as a Dialog or Sheet over the main app layout on first load.
|
||
**Why it's wrong:** No URL, so refresh drops the user back to the start or the main app with incomplete setup. Focus management in a full-screen modal is complex. Back button behavior is broken.
|
||
**Do this instead:** Dedicated `/setup` route. `WizardRoute` handles redirect logic. The browser URL reflects wizard state, refresh works, and no special cleanup is needed.
|
||
|
||
### Anti-Pattern 6: Adding useEffect Dependencies by Feel
|
||
|
||
**What people do:** Add `generateFromTemplate` itself to the `useEffect` deps array.
|
||
**Why it's wrong:** Mutation objects from TanStack Query are re-created on every render, causing the effect to re-run in a loop.
|
||
**Do this instead:** Use a `useRef` guard (`attempted.current`) and gate on stable primitive values (`!loading`, `!currentBudget`, `hasTemplateItems`). Do not include the mutation object in the dependency array.
|
||
|
||
---
|
||
|
||
## Supabase / DB Notes (v2.0)
|
||
|
||
**No new migrations required.** All v2.0 features write to existing tables:
|
||
|
||
| Feature | Writes To | Via |
|
||
|---------|-----------|-----|
|
||
| Wizard | `categories`, `template_items` | existing `useCategories().create`, `useTemplate().createItem` |
|
||
| Auto-budget | `budgets`, `budget_items` | existing `useBudgets().generateFromTemplate` |
|
||
| Inline one-off | `budget_items` | existing `useBudgets().createItem` |
|
||
| Design tokens | — | CSS only |
|
||
|
||
The `quick_add_items` table becomes read-only from the primary UI flow. No rows deleted, no schema change.
|
||
|
||
---
|
||
|
||
## Integration Points: New vs Existing
|
||
|
||
| New Feature | Existing Hooks/Mutations Used | New Additions |
|
||
|-------------|------------------------------|---------------|
|
||
| First-run wizard | `useCategories().create`, `useTemplate().createItem` | `useFirstRunState`, `presets.ts`, wizard components |
|
||
| Auto-budget creation | `useBudgets().generateFromTemplate` (already exists) | `useEffect` trigger in DashboardPage, `hasTemplateItems` guard |
|
||
| Inline one-off add | `useBudgets().createItem` (already exists), `useCategories()` | `AddOneOffSheet` component |
|
||
| Dashboard simplification | `useBudgetDetail`, `useMonthParam`, `SummaryStrip`, `CollapsibleSections` | Remove 3-column chart grid from DashboardContent |
|
||
| Design tokens | All existing components consume tokens automatically | Token value adjustments in `index.css` only |
|
||
|
||
---
|
||
|
||
## Sources
|
||
|
||
- Full codebase inspection (April 2026):
|
||
- `src/App.tsx`, `src/components/AppLayout.tsx`
|
||
- `src/pages/DashboardPage.tsx`, `src/pages/TemplatePage.tsx`, `src/pages/BudgetDetailPage.tsx`
|
||
- `src/hooks/useTemplate.ts`, `src/hooks/useBudgets.ts`, `src/hooks/useQuickAdd.ts`, `src/hooks/useMonthParam.ts`
|
||
- `src/components/QuickAddPicker.tsx`, `src/components/shared/PageShell.tsx`
|
||
- `src/lib/types.ts`, `src/lib/palette.ts`
|
||
- `src/index.css`, `src/i18n/en.json`
|
||
- `supabase/migrations/002_categories.sql` through `005_quick_add.sql`
|
||
- Confidence: HIGH — all claims are grounded in verified source code, not assumptions
|
||
|
||
---
|
||
|
||
*Architecture research for: SimpleFinanceDash v2.0 — wizard setup, auto-budget, inline add, design rework*
|
||
*Researched: 2026-04-02*
|