Architecture
Budgetzilla follows a local-first architecture where all data operations happen in the browser or local desktop process without requiring a shared backend server.
High-Level Overview
Section titled “High-Level Overview”graph TD subgraph "UI Layer (React)" Pages[Pages: Dashboard, Trans, etc.] Comp[Components: UI, Charts, Forms] Hooks[Hooks: Query, State] Pages --> Comp Pages --> Hooks end subgraph "API Layer" Unified[Unified API Interface] Local[Local SQLite Implementation] Mock[Mock In-Memory Mode] AI[Local AI Parser: Gemma 4] Unified --> Local Unified --> Mock Unified --> AI end subgraph "Storage Layer" WASM[SQLite WASM] OPFS[OPFS / Native FS] GPU[WebGPU / GPU Cache] WASM --> OPFS AI --> GPU end Hooks --> Unified Local --> WASM AI --> Unified Services[Services: Drive Sync, Export, AI Inference] Services --> Unified
Project Structure
Section titled “Project Structure”budget/├── webapp/ # React application│ ├── src/│ │ ├── api/ # API client layer│ │ │ ├── index.ts # Unified API exports│ │ │ ├── config.ts # API configuration (local/mock)│ │ │ └── local/ # SQLite implementation│ │ ├── db/ # Database operations│ │ │ ├── sqlite.ts # SQLite WASM + OPFS persistence│ │ │ └── schema.ts # Database schema definitions│ │ ├── services/ # Core logic│ │ │ ├── webgpuInference.ts # Gemma 4 via WebGPU│ │ │ ├── localAiParser.ts # AI transaction extraction│ │ │ └── driveSync.ts # Google Drive sync│ │ ├── components/ # React components│ │ ├── pages/ # Page components│ │ ├── hooks/ # Custom React hooks│ │ └── providers/ # React context providers│ ├── src-tauri/ # Tauri desktop wrapper│ │ ├── src/ # Rust backend code│ │ └── tauri.conf.json # Tauri configuration│ ├── public/ # Static assets│ └── index.html # Entry point└── docs/ # Documentation (Starlight)Technology Stack
Section titled “Technology Stack”Frontend
Section titled “Frontend”| Technology | Purpose |
|---|---|
| React 18 | UI framework |
| TypeScript | Type safety |
| Vite | Build tool and dev server |
| Tailwind CSS | Styling |
| Radix UI | Accessible UI primitives |
| TanStack Query | Data fetching and caching |
| ECharts | Charts and visualizations |
| Transformers.js | Browser-side AI inference |
Database & AI
Section titled “Database & AI”| Technology | Purpose |
|---|---|
| sql.js | SQLite compiled to WebAssembly |
| OPFS | Browser file system for persistence |
| Gemma 4 | Local LLM for transaction parsing |
| WebGPU | Hardware acceleration for AI |
| Ollama | Fallback local AI server |
Desktop App (Tauri)
Section titled “Desktop App (Tauri)”| Technology | Purpose |
|---|---|
| Rust | Native backend and system integration |
| Tauri | Desktop application framework |
| WebView2 / WKWebView | Native web views (Windows / macOS) |
AI Engine Architecture
Section titled “AI Engine Architecture”Budgetzilla uses a local-first AI stack to process receipts and statements:
- Inference Pipeline:
- WebGPU (Primary): Runs the
onnx-community/gemma-4-E2B-it-ONNXmodel directly in the browser using Transformers.js. - Ollama (Fallback): Connects to a local Ollama instance if WebGPU is unavailable or disabled.
- WebGPU (Primary): Runs the
- Data Flow:
- File (Image/PDF) -> Text Extraction -> Structured Prompt -> LLM Inference -> JSON Transaction Array -> Database.
- Persistence:
- Model weights are cached in IndexedDB via
idbModelCache.tsto prevent re-downloading (~1.2GB).
- Model weights are cached in IndexedDB via
Data Flow
Section titled “Data Flow”Reading Data
Section titled “Reading Data”- Component calls API function (e.g.,
getTransactions()) - API layer routes to local implementation
- Local implementation executes SQL query
- Results transformed to TypeScript types
- Data returned to component via TanStack Query
Writing Data
Section titled “Writing Data”- Component calls mutation (e.g.,
createTransaction()) - API validates input data
- SQL INSERT/UPDATE executed
- Database persisted to OPFS
- TanStack Query invalidates relevant caches
- UI updates automatically
Database Schema
Section titled “Database Schema”Transactions Table
Section titled “Transactions Table”CREATE TABLE transactions ( id TEXT PRIMARY KEY, amount REAL NOT NULL, description TEXT NOT NULL, category_id TEXT NOT NULL, date TEXT NOT NULL, notes TEXT, created_at TEXT NOT NULL, updated_at TEXT NOT NULL, FOREIGN KEY (category_id) REFERENCES categories(id));Categories Table
Section titled “Categories Table”CREATE TABLE categories ( id TEXT PRIMARY KEY, name TEXT NOT NULL, color TEXT NOT NULL, icon TEXT, type TEXT NOT NULL CHECK (type IN ('expense', 'income')), created_at TEXT NOT NULL);Budgets Table
Section titled “Budgets Table”CREATE TABLE budgets ( id TEXT PRIMARY KEY, category_id TEXT NOT NULL, amount REAL NOT NULL, month TEXT NOT NULL, created_at TEXT NOT NULL, FOREIGN KEY (category_id) REFERENCES categories(id));API Design
Section titled “API Design”The API layer provides a consistent interface regardless of the underlying implementation:
// TransactionsgetTransactions(filters?: TransactionFilters): Promise<Transaction[]>getTransaction(id: string): Promise<Transaction>createTransaction(data: CreateTransactionInput): Promise<Transaction>updateTransaction(id: string, data: UpdateTransactionInput): Promise<Transaction>deleteTransaction(id: string): Promise<void>
// CategoriesgetCategories(): Promise<Category[]>createCategory(data: CreateCategoryInput): Promise<Category>// ... similar pattern
// BudgetsgetBudgets(month?: string): Promise<Budget[]>setBudget(categoryId: string, amount: number, month: string): Promise<Budget>// ... similar patternKey Design Decisions
Section titled “Key Design Decisions”Why Local-First?
Section titled “Why Local-First?”- Privacy: Financial data stays on user’s device
- Speed: No network latency for operations
- Offline: Works without internet
- Simplicity: No backend infrastructure to maintain
Why SQLite WASM?
Section titled “Why SQLite WASM?”- Familiar: Standard SQL syntax
- Robust: Battle-tested database
- Portable: Same queries work everywhere
- Performant: Fast for typical budgeting data sizes
Why OPFS?
Section titled “Why OPFS?”- Persistent: Survives browser restarts
- Private: Not accessible to other sites
- Fast: Direct file system access (vs. IndexedDB)