← Back to projects

Sumant

Local-first personal finance app: log an expense or import your bank statement without connecting a bank account, and without the data leaving the device except to sync.

Sumant home page, a personal finance application

The problem

Tracking personal finances usually means choosing between a spreadsheet you have to keep up by hand and an app that asks you to connect your bank, verify your identity and trust that your transactions live somewhere you know nothing about.

Sumant starts from the opposite idea: the app takes seconds to use, needs no banking credentials, and writes data to the device first. The account exists to sync between phone and desktop, not to unlock the product.

The solution

Accounts and transactions

product core

  • Multiple accounts per user — cash, bank, card, savings or other — each with its own currency (14 available).
  • Expenses, income and transfers between accounts, with custom categories and subcategories.
  • Amounts stored as integer cents: direction comes from the transaction type, not from the number's sign.

Budgets, goals and fixed costs

planning

  • Per-category budgets on a daily, weekly or monthly period.
  • Savings goals with a target amount and deadline.
  • Recurring expenses and subscriptions, plus net worth tracking with assets and liabilities.

Statement import

CSV · XLSX · Norma 43

  • Automatic detection of the separator, the header row and what each column means, with reusable templates per bank.
  • A reader for the AEB Norma 43 format, the standard statement file Spanish banks export.
  • Duplicate detection and merchant grouping, so transactions get reviewed and categorized in batches instead of one by one.

Day-to-day use

PWA · ES/EN

  • Dashboard, statistics, search and filters over the full history.
  • Private mode that blurs every amount on screen.
  • Installable as a PWA and usable offline; interface in Spanish and English.

Architecture

The system is three surfaces in one monorepo: the app, the public site and an internal operations panel. The decision that shapes everything else is that the UI never waits for the network: it writes to the browser database and syncing happens afterwards, in the background.

Client

React · Vite · TypeScript
React Router · Zustand
TanStack Query · Zod

Local-first

IndexedDB (Dexie)
Sync queue
On-device AI (ONNX · WASM)

Cloud

Supabase Auth · PostgreSQL
Per-user RLS
Edge Functions · Stripe

Technical challenges

Syncing without breaking the local experience

Writes never wait for the network, so the app has to remember what is still pending upload. It keeps a queue of operations and a progress marker per table, so on reconnect it only asks for what changed since last time. Downloads are paginated and uploads are batched to stay under the backend's request size limit, and the queried window overlaps slightly to tolerate clock drift between devices.

Reading a statement from any bank

Every bank exports differently: different separators, headers on different rows, different date and amount formats. Instead of maintaining one reader per bank, the importer infers the file structure, proposes a column mapping from synonyms and lets you save it as a template. For the Spanish market it adds a reader for the AEB Norma 43 format, which being a standard covers most banks with a single parser.

Not duplicating transactions

Re-importing an overlapping statement is the norm, not the exception. Each transaction produces a fingerprint from its stable fields, which makes it possible to detect what already exists before writing anything. Whatever is left gets grouped by merchant so dozens can be categorized at once.

Categorizing without the data leaving the device

Category suggestions and meaning-based search run on embeddings computed in the browser itself, with a self-hosted model executed on ONNX Runtime compiled to WebAssembly. No transaction is sent to an external AI service, and the embeddings never even enter sync: they stay on the device that produced them. This is the decision that makes the privacy claim a property of the architecture rather than a line in the privacy policy.

Changing the local schema without losing data

The database lives in the user's browser, so there is no maintenance window and no way to migrate in bulk from the server. Every model change is a schema version with its own migration function, and app startup has to be able to bring any old version up to the current one.

Tech stack

App React · Vite · TypeScript (PWA)
Estado / State Zustand · TanStack Query · React Hook Form + Zod
Datos locales / Local data IndexedDB (Dexie)
Backend Supabase — Auth · PostgreSQL · RLS · Edge Functions
IA on-device / On-device AI Transformers.js · ONNX Runtime (WebAssembly)
Landing Next.js App Router · next-intl
Panel interno / Admin Next.js · 2FA (TOTP)
Pagos / Payments Stripe (checkout · portal · webhooks)
UI Tailwind CSS · Radix UI · Recharts
Tests Vitest · Testing Library · Playwright
Observabilidad / Observability Sentry
Hosting Cloudflare Pages · Vercel · Supabase (UE / EU)

What sits behind this project

Own project under active development. The repository is private; this page summarizes the architecture and the technical decisions. Sumant is free with a one-time PRO plan, and usage figures are not part of this summary.