# Architecture **Analysis Date:** 2026-03-16 ## Pattern Overview **Overall:** Layered client-side SPA with React, using hooks for data access and state management through TanStack Query, centralized authentication with Supabase, and component-based UI rendering. **Key Characteristics:** - Three-tier vertical slice: Pages → Hooks → Library (Supabase + utilities) - TanStack Query for caching and synchronization of remote state - Route-based code organization with role-based access control (protected/public routes) - Supabase PostgreSQL backend with real-time subscriptions capability - Internationalization (i18n) at the root level with JSON resource files ## Layers **Presentation Layer (Pages & Components):** - Purpose: Render UI, handle user interactions, coordinate component composition - Location: `src/pages/` and `src/components/` - Contains: Page components (DashboardPage, TemplatePage, BudgetListPage, etc.), UI primitives from `src/components/ui/`, and custom components (AppLayout, QuickAddPicker) - Depends on: Hooks, UI utilities, i18n, icons (lucide-react), formatting utilities - Used by: React Router for page routing **Hooks Layer (Data Access & State):** - Purpose: Encapsulate all server communication and query/mutation logic, manage request/response caching via TanStack Query - Location: `src/hooks/` - Contains: Custom hooks for each domain (useAuth, useCategories, useBudgets, useTemplate, useQuickAdd) - Depends on: Supabase client, TanStack Query, type definitions - Used by: Page and component layers for CRUD operations and state retrieval **Library Layer (Core Services & Utilities):** - Purpose: Provide primitive implementations, type definitions, and configuration - Location: `src/lib/` - Contains: - `supabase.ts` - Supabase client initialization and environment validation - `types.ts` - TypeScript interfaces for all domain entities (Profile, Category, Budget, BudgetItem, Template, TemplateItem, QuickAddItem) - `format.ts` - Currency formatting using Intl.NumberFormat - `palette.ts` - Category color mapping and labels (internationalized) - `utils.ts` - General utilities - Depends on: Supabase SDK, TypeScript types - Used by: Hooks and components for types and helpers **Authentication Layer:** - Purpose: Manage session state and auth operations - Location: `src/hooks/useAuth.ts` - Implementation: Reactive Supabase auth listener with automatic session refresh - State: Session, user, loading flag - Operations: signUp, signIn, signInWithOAuth (Google, GitHub), signOut **Internationalization (i18n):** - Purpose: Provide multi-language support at runtime - Location: `src/i18n/index.ts` with JSON resource files (`en.json`, `de.json`) - Framework: react-i18next with i18next - Initialization: Automatic at app startup before any render ## Data Flow **Read Pattern (Fetch & Display):** 1. Component renders and mounts 2. Component calls custom hook (e.g., `useBudgets()`, `useCategories()`) 3. Hook initializes TanStack Query with async queryFn 4. Query function calls Supabase table select with filters/joins 5. Supabase returns typed rows from database 6. Hook caches result in Query store with staleTime of 5 minutes 7. Component receives `data`, `loading`, and error states 8. Component renders based on data state 9. Subsequent mounts of same component use cached data (until stale) **Write Pattern (Mutate & Sync):** 1. Component invokes mutation handler (e.g., click "Save") 2. Handler calls `mutation.mutateAsync(payload)` 3. Mutation function marshals payload and calls Supabase insert/update/delete 4. Supabase executes DB operation and returns modified row(s) 5. Mutation onSuccess callback triggers Query invalidation 6. Query re-fetches from server with fresh data 7. Component re-renders with new cached data 8. Toast notification indicates success or error **Real-time Budget Updates:** Example flow for editing a budget item (from `useBudgets.ts`): 1. User edits amount in budget detail table → calls `updateItem.mutateAsync({ id, budgetId, budgeted_amount: X })` 2. Hook serializes to Supabase `.update()` with .eq("id", id) 3. Response contains updated BudgetItem with joined Category data 4. onSuccess invalidates `["budgets", budgetId, "items"]` cache key 5. DashboardContent's useBudgetDetail query re-fetches entire items array 6. Component recalculates totals and re-renders pie chart and progress bars **State Management:** - No Redux or Zustand — TanStack Query handles async state - Local component state for UI interactions (dialogs, forms, selections) - Session state maintained by Supabase auth listener in useAuth hook - Cache invalidation is the primary sync mechanism ## Key Abstractions **Query Key Pattern:** Each hook defines typed query keys as const arrays: - `["categories"]` - all user categories - `["budgets"]` - all user budgets - `["budgets", id]` - single budget - `["budgets", id, "items"]` - items for a specific budget - `["template"]` - user's monthly template - `["template-items"]` - items in template - `["quick-add"]` - user's quick-add library Purpose: Enables granular invalidation (e.g., update one budget doesn't refetch all budgets) **Mutation Factories:** Hooks expose mutations as properties of returned object: - `useCategories()` returns `{ categories, loading, create, update, remove }` - `useBudgets()` returns `{ budgets, loading, createBudget, generateFromTemplate, updateItem, createItem, deleteItem, deleteBudget }` Each mutation includes: - `mutationFn` - async operation against Supabase - `onSuccess` - cache invalidation strategy **Hook Composition:** `useBudgetDetail(id)` is both a standalone export and accessible via `useBudgets().getBudget(id)`: - Enables flexible usage patterns - Query with `.enabled: Boolean(id)` prevents queries when id is falsy - Used by DashboardPage to fetch current month's budget data **Type Hierarchy:** Core types in `src/lib/types.ts`: - `Profile` - user profile with locale and currency - `Category` - user-defined expense category - `Template` & `TemplateItem` - monthly budget template (fixed/variable items) - `Budget` & `BudgetItem` - actual budget with tracked actual/budgeted amounts - `QuickAddItem` - quick entry library for rapid data entry Relationships: - User → many Profiles (one per row, per user) - User → many Categories - User → one Template (auto-created) - Template → many TemplateItems → Category (join) - User → many Budgets (monthly) - Budget → many BudgetItems → Category (join) - User → many QuickAddItems ## Entry Points **Application Root:** - Location: `src/main.tsx` - Initializes React 19 with context providers: - QueryClientProvider (TanStack Query) - BrowserRouter (React Router) - TooltipProvider (Radix UI) - Toaster (Sonner notifications) - Creates single QueryClient with 5-minute staleTime default **Route Configuration:** - Location: `src/App.tsx` - Defines protected and public routes - Protected routes wrapped in `ProtectedRoute` which checks auth state via `useAuth()` - Public routes wrapped in `PublicRoute` which redirects authenticated users away - Routes: - `/login`, `/register` - public (redirect home if logged in) - `/`, `/categories`, `/template`, `/budgets`, `/budgets/:id`, `/quick-add`, `/settings` - protected **Layout Root:** - Location: `src/components/AppLayout.tsx` - Renders Sidebar with navigation items (using Radix UI primitives) - Renders Outlet for nested route content - Provides sign-out button in footer **Page Entry Points:** Each page component is stateless renderer with logic split between: 1. Page component (routing layer, layout) 2. Sub-component(s) (content logic) 3. Hook(s) (data fetching) Example: `DashboardPage.tsx` → - Main component finds current month's budget - Delegates to `DashboardContent` with `budgetId` prop - DashboardContent calls `useBudgetDetail(budgetId)` for data ## Error Handling **Strategy:** Try-catch in mutation handlers with toast notifications for user feedback. **Patterns:** 1. **Mutation Errors:** - Try block executes `mutation.mutateAsync()` - Catch block logs to console and shows `toast.error(t("common.error"))` - Button remains clickable if error is transient - Example in `TemplatePage.tsx` lines 182-204 2. **Query Errors:** - Query errors are captured by TanStack Query internally - Hooks return loading state but not explicit error state - Pages render null/loading state during failed queries - Retry is configured to attempt once by default 3. **Auth Errors:** - `useAuth.ts` catches Supabase auth errors and re-throws - LoginPage catches and displays error message in red text - Session state remains null if auth fails 4. **Missing Environment Variables:** - `src/lib/supabase.ts` throws during module load if VITE_SUPABASE_URL or VITE_SUPABASE_ANON_KEY missing - Prevents runtime errors from undefined client ## Cross-Cutting Concerns **Logging:** Minimal logging — relies on: - Browser DevTools React Query Devtools (installable) - Console errors from try-catch blocks - Sonner toast notifications for user-facing issues **Validation:** - Input validation in mutation handlers (e.g., category ID check, amount > 0) - Type validation via TypeScript compile-time - Supabase schema constraints (NOT NULL, FOREIGN KEY, CHECK constraints) - Database uniqueness constraints (e.g., one template per user) **Authentication:** - Supabase session token stored in localStorage (browser SDK default) - Auth state checked at page render time via `useAuth()` hook - Protected routes redirect unauthenticated users to /login - Session persists across page refreshes (Supabase handles recovery) **Authorization:** - Row-level security (RLS) policies on Supabase tables enforce user_id filters - No explicit authorization logic in client (relies on DB policies) - All queries filter by current user via `await supabase.auth.getUser()` --- *Architecture analysis: 2026-03-16*