Nook
A mobile-first finance app where the AI never touches the arithmetic. Every number is computed in SQL; the model only ever sees pre-computed facts.
The problem
Personal finance apps have a trust problem, and AI made it worse. If a model is generating your insights, you cannot tell whether the number it just quoted you is real or something it produced because the sentence needed a number. And most of these apps are built on guilt — they tell you what you did wrong.
I wanted to build one where the numbers are verifiably yours and the advice does not make you feel bad for eating out.
The principle everything else follows from
The AI never touches the arithmetic.
Every figure in Nook is computed in SQL or application code. A single Postgres function returns the entire dashboard in one call: totals, previous month, breakdown by tag, breakdown by payment method, daily series, six-month trend, and running bank and cash balances. A separate layer pre-computes those into a set of plain facts.
The model receives only that fact set. It never sees a raw transaction, a title or a note. It can interpret your spending. It cannot tell you what you spent.
That one decision resolves the trust problem and a privacy problem at the same time, and it is the reason every other choice below was possible.
Designing what the model is not allowed to say
Two layers, because one is not enough.
Before: a system prompt with hard constraints — invent no numbers, give no investment advice, and never shame the user. The last one is a design decision, not a safety one. A finance app that makes you feel watched is a finance app you stop opening.
After: a filter that runs over the model's reply and deletes anything weak or banned before it reaches the screen. Prompts fail. A prompt is a request, not a guarantee, so I treated the output as untrusted and checked it.
Insights are cached per user, period and scope, keyed on a hash of the underlying data, so they regenerate only when the data actually changes. The prompt carries a version number — bumping it invalidates every cached insight at once.
AI last, not first
When you add an entry, Nook guesses the tag in three stages:
- What you called this before.
- Built-in keyword matches — Swiggy resolves to Food.
- The model, only if the first two find nothing.
Most apps would call the model immediately. Going in this order is faster, costs nothing for the common case, and — the part that actually matters — is predictable. Your own past behaviour beats a guess, every time.
Savings are not an expense
Standard finance apps subtract everything you did not keep in your account, which means money you deliberately saved shows up as money you lost. One tag is flagged as savings and reported separately, so the equation reads:
income − expenses − savings = what is actually left
That is a conceptual model change, not a feature. It alters what the headline number on the dashboard means.
Designing for how people are actually paid
Salary reminders support five cadences — monthly, semi-monthly, weekly, fortnightly, and custom intervals — and can be snoozed. Not everyone is paid on the last working day of the month, and an app that assumes so is unusable for anyone paid weekly or by invoice.
Other decisions worth naming
- Amounts are stored as integers in minor units, never floats. Money and floating point do not mix.
- A design token system in CSS variables rather than hard-coded values, so theming and spacing are decided once.
- Bottom navigation below 900px with the add action as a centre floating button; side navigation on desktop. Adding an entry is the primary action on mobile and is placed where a thumb already is.
- Row-level security on every table, and the database revokes update permission on everything except tag name and colour — so a tag's type or owner cannot be changed through the public key.
- PDF statement export, so the data leaves in a form you can hand to someone else.
Outcome
Designed, built and shipped solo in September 2026. I use it as my own tracker; it is not open to other users yet.
What I would do differently
The insight layer is the weakest part of it. The dashboard surfaces one AI-generated insight drawn from a set of aggregate facts, and because those facts are aggregates, the advice tends toward the generic. It can tell you food spending went up. It cannot tell you it went up because of three weekends in a row.
The fix is not a better prompt, it is a better fact set — time-of-week patterns, recurring versus one-off spending, deltas measured against your own baseline rather than simply against last month — and letting the model choose between several kinds of observation instead of always producing the same shape of sentence. The guardrails would stay exactly as they are. The input would get better.
I would also fold the schema migrations into the base file. They currently live across three files and the README mentions only one, so a fresh clone does not set up cleanly.