FinGes
A personal finance app for tracking accounts, expenses, budgets and an investment portfolio, with real returns maths instead of a running total.
- Role
- Lead developer
- Year
- 2026
- Stack
- React · Vite · Tailwind
Most budgeting apps either connect to your bank and take a cut of your data, or they are a spreadsheet with nicer fonts. FinGes is the second thing done properly: a private ledger where bank accounts are the single source of cash truth, and every expense, income and transfer resolves against a real account balance.
It covers bank accounts, expenses, recurring bills, income, savings buckets and projections, budgets with rollovers, debts, and a multi-currency investment portfolio with holdings, dividends, sales and allocation targets. It runs on React 19 and Vite with Zustand for state, and Supabase for auth, storage and the database. Every monetary value is stored as integer cents rather than a float, and dates as ISO strings, which removes an entire category of rounding bug before it can start. The interface is fully bilingual in English and Spanish.
The architecture was rewritten partway through. It began local-first, with IndexedDB as the store and a bidirectional sync engine on top, and that engine turned into the most complex and least reliable part of the codebase: tombstones, conflict resolution UI, sync cursors, and records that could resurrect on reload. It was replaced with Postgres as the single source of truth, hydrating the store in one round-trip on boot and patching state from a realtime subscription, with per-device preferences kept separate in localStorage. Deleting the sync layer removed a large amount of bookkeeping and made cross-device updates correct by construction.
Engineering notes
Integer cents, never floats
All money is stored as integer cents and converted only at the formatting layer. Currency conversion for the portfolio is applied per holding against a base currency, so a multi-currency position reports correctly without accumulating float drift.
XIRR solved properly
Annualised return is computed with a hand-written Newton-Raphson solver over dated cashflows, with sign validation, clamping and convergence guards. When the true rate falls outside a sane range the function returns null rather than a confident wrong number.
Deleting the sync engine
The original local-first design needed tombstones, conflict resolution and cursor bookkeeping to stay consistent, and still lost records on reload. Making Postgres authoritative and subscribing to realtime changes removed the whole class of bug along with the code that caused it.