Skip to content

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.

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
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)
TechnologyPurpose
React 18UI framework
TypeScriptType safety
ViteBuild tool and dev server
Tailwind CSSStyling
Radix UIAccessible UI primitives
TanStack QueryData fetching and caching
EChartsCharts and visualizations
Transformers.jsBrowser-side AI inference
TechnologyPurpose
sql.jsSQLite compiled to WebAssembly
OPFSBrowser file system for persistence
Gemma 4Local LLM for transaction parsing
WebGPUHardware acceleration for AI
OllamaFallback local AI server
TechnologyPurpose
RustNative backend and system integration
TauriDesktop application framework
WebView2 / WKWebViewNative web views (Windows / macOS)

Budgetzilla uses a local-first AI stack to process receipts and statements:

  1. Inference Pipeline:
    • WebGPU (Primary): Runs the onnx-community/gemma-4-E2B-it-ONNX model directly in the browser using Transformers.js.
    • Ollama (Fallback): Connects to a local Ollama instance if WebGPU is unavailable or disabled.
  2. Data Flow:
    • File (Image/PDF) -> Text Extraction -> Structured Prompt -> LLM Inference -> JSON Transaction Array -> Database.
  3. Persistence:
    • Model weights are cached in IndexedDB via idbModelCache.ts to prevent re-downloading (~1.2GB).
  1. Component calls API function (e.g., getTransactions())
  2. API layer routes to local implementation
  3. Local implementation executes SQL query
  4. Results transformed to TypeScript types
  5. Data returned to component via TanStack Query
  1. Component calls mutation (e.g., createTransaction())
  2. API validates input data
  3. SQL INSERT/UPDATE executed
  4. Database persisted to OPFS
  5. TanStack Query invalidates relevant caches
  6. UI updates automatically
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)
);
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
);
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)
);

The API layer provides a consistent interface regardless of the underlying implementation:

// Transactions
getTransactions(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>
// Categories
getCategories(): Promise<Category[]>
createCategory(data: CreateCategoryInput): Promise<Category>
// ... similar pattern
// Budgets
getBudgets(month?: string): Promise<Budget[]>
setBudget(categoryId: string, amount: number, month: string): Promise<Budget>
// ... similar pattern
  • Privacy: Financial data stays on user’s device
  • Speed: No network latency for operations
  • Offline: Works without internet
  • Simplicity: No backend infrastructure to maintain
  • Familiar: Standard SQL syntax
  • Robust: Battle-tested database
  • Portable: Same queries work everywhere
  • Performant: Fast for typical budgeting data sizes
  • Persistent: Survives browser restarts
  • Private: Not accessible to other sites
  • Fast: Direct file system access (vs. IndexedDB)