Finance Tracker
“A financial command center that behaves like a physical ledger: instant, zero cloud accounts, and mathematically unreadable without the owner's PIN.”
A zero-cloud, encrypted personal finance PWA engineered with client-side Web Crypto and shadowed IndexedDB persistence.
- Role
- Solo Architect & Engineer
- Context
- 4 Weeks (v1.2.0)
- Team
- Solo Project
- Core Stack
- Vanilla JS (ESM), Web Crypto API, IndexedDB, Service Worker
Fig 1.0 — Architecture execution snapshot (Finance Tracker)
The Friction
Why build a personal finance system without a cloud backend?
Nearly every commercial budgeting application treats personal finances as marketing telemetry: your transactions are uploaded to third-party databases, analyzed for ad targeting, or locked behind recurring monthly subscriptions. When a provider shuts down or experiences a breach, your entire financial history is exposed or lost.
I wanted an application that functioned like a bound paper ledger: 100% owned by the user, working instantly without an internet connection, and structurally incapable of leaking data. The goal was to prove that modern browser standards can deliver production-grade security and full offline utility without a single byte leaving the device.
Deliberate Constraints
The system architecture was not chosen in an unconstrained vacuum. Each structural decision emerged directly from four non-negotiable technical boundaries.
Strict zero-server architecture. No external API endpoints, no analytics beacons, and no remote backups.
Mandated on-device key derivation, client-side cryptographic hashing, and local JSON export/import pipelines for user data portability.
Zero npm packages, zero UI frameworks (no React, no Vue), and zero charting libraries (no Chart.js, no D3).
Every interactive element—including SVG circular velocity rings, sparkline paths, and Canvas bar graphs—had to be authored from scratch using raw DOM APIs.
All financial figures, account names, and goal targets must be encrypted before touching browser storage.
Utilized Web Crypto API to derive non-extractable 256-bit AES-GCM keys from user PINs using 100,000 PBKDF2-SHA256 iterations and random 96-bit initialization vectors.
Mobile WebKit (iOS Safari) aggressively purges localStorage after 7 days of inactivity without user interaction.
Engineered an asynchronous IndexedDB shadow engine that replicates encrypted blobs across storage layers and restores state upon cold launch.
System Architecture & Data Pipeline
The application is built on pure ES Modules separated into domain stores, cryptographic adapters, and reactive view controllers. Application state is held in memory during an unlocked session and atomically encrypted to disk upon state mutation or tab backgrounding.
Alto, Cultus, Corolla. Standard tiered rental base.
Audi A6, BMW 7, Land Cruiser. Chauffeur insurance rate.
Sportage, Tucson, Fortuner. All-terrain security deposit.
Bolan, Hiace, Coaster. High-capacity commercial rate.
Dynamic Polymorphism at Runtime: The orchestrator holds a single container std::vector<Vehicle*> fleet. When executing reservations or computing quotes, method calls to v->calculateCost(days) dynamically dispatch to the concrete subclass implementation through each instance's vtable pointer.
Subsystem Decomposition
Cryptographic Adapter Layer
Web Crypto API (SubtleCrypto)Executes PBKDF2 key derivation and AES-GCM 256-bit encryption/decryption in native browser C++ threads.
Dual-Tier Storage Shadow Engine
localStorage + IndexedDBPersists encrypted ciphertext strings with dual-layer fallback to prevent mobile browser storage eviction.
Native Chart Rendering Engine
Zero-Dependency Canvas & SVGRenders financial velocity, net worth progression, and asset allocations without pulling heavy visualization bundles.
Lifecycle & Service Worker Cache
CacheStorage & Auto-LockEnables instant cold-starts offline and locks sensitive memory structures when the app is placed in the background.
The Hard Part: Mobile WebKit Storage Eviction & Non-Extractable Key Derivation
Preventing silent data loss on iOS Safari while keeping PBKDF2 key derivation responsive on low-power devices.
Under Apple WebKit's Intelligent Tracking Prevention (ITP) and 7-day storage policies, client-side localStorage can be evicted if a user doesn't open the PWA within a 7-day window. Simultaneously, running 100,000 PBKDF2 iterations with SHA-256 on mobile hardware can stutter the main UI thread during unlock.
If the user relies exclusively on localStorage, an iOS update or memory purge silently wipes their ledger. Furthermore, deriving keys synchronously would lock the browser keypad animation during PIN entry.
// Key Derivation with PBKDF2 (100,000 rounds)
const keyMaterial = await crypto.subtle.importKey(
'raw',
new TextEncoder().encode(pin),
{ name: 'PBKDF2' },
false,
['deriveKey']
);
const masterKey = await crypto.subtle.deriveKey(
{ name: 'PBKDF2', salt, iterations: 100_000, hash: 'SHA-256' },
keyMaterial,
{ name: 'AES-GCM', length: 256 },
false, // Non-extractable: key material cannot be read back by scripts
['encrypt', 'decrypt']
);
// AES-GCM with dynamic 96-bit IV
const iv = crypto.getRandomValues(new Uint8Array(12));
const ciphertext = await crypto.subtle.encrypt(
{ name: 'AES-GCM', iv },
masterKey,
new TextEncoder().encode(JSON.stringify(ledgerState))
);We shadowed localStorage with an IndexedDB object store that survives WebKit eviction sweeps, and used Web Crypto's native asynchronous thread pooling so the keypad stays completely fluid during cryptographic operations.
Browser APIs have become remarkably capable. When you respect the platform rather than layering heavy abstractions over it, you can achieve zero-trust security and sub-millisecond execution with zero bundle overhead.
Cryptographic Test Suite & Security Verification
Automated unit test suite verifying AES-GCM roundtrips, PIN verification limits, and storage shadow failover.
Engineering Reflection
“Software sovereignty starts when an application can function indefinitely without a subscription, a remote server, or an internet connection.”
Building the Finance Tracker taught me how fragile the modern software ecosystem has become. We have normalized building applications that instantly fail the moment a cloud API goes down or an SSL certificate expires.
By deliberately forbidding external dependencies and servers, I had to understand the raw browser platform deeply: how SubtleCrypto allocates non-extractable keys, how Service Workers intercept fetch events, and how mobile operating systems handle cache persistence under low-memory conditions.
The result is an application that will work identically ten years from now, regardless of what happens to cloud infrastructure.
Interested in discussing this architecture?
I'm always open to technical dialogue, code reviews, and exploring system constraints.