Budget
A budgeting app that runs entirely in the browser, wearing early-2000s corporate chrome and a Frutiger Aero skin.
- Section
- productivity
- Year
- 2026
- Role
- Solo project
- Stack
- Next.js, React, TypeScript, IndexedDB
Summary
A budgeting app that runs entirely in the browser. Import a CSV, file each row, set a limit per category, watch the month. Transactions, categories and limits live in IndexedDB behind an early-2000s corporate skin — no account, no network, JSON backup in and out.
Story
The design work was mostly picking representations that can’t drift: money as a
signed integer of cents, dates as YYYY-MM-DD strings and months as YYYY-MM,
so sums are a reduce and nothing loses a day to a timezone. The bugs that
mattered all passed the type checker. A positive amount filed against an expense
category is a refund, not income, and the first build counted it both ways —
one $125 refund moved the net by $250 and made the summary tiles disagree with
the donut chart beside them. “Copy limits from July” cleared August before
checking the source had anything in it, so a button labelled copy was a way to
lose a month with no undo. And semantic colours kept losing cascade fights to
more specific base rules, which a contrast sweep turned into a full audit —
income green was sitting at 2.77:1.
Lessons
- Type checks don’t catch semantics. Every one of the real bugs compiled cleanly; only writing the rule down once — income category is income, expense category is a refund — settled it.
- A destructive step has to validate before it destroys, not after.
- A semantic colour needs separate tokens for text and for fill, or it passes contrast in one role and fails in the other.