Net worth, computed rather than copied. This is the one the rest of the fleet was in service of — the app I was really trying to build when I started learning to build things, and the only one whose problem I'd already been solving by hand for years.
Figures measured 2026-07-14.
I have been tracking my net worth in an Excel workbook since 2018. Not casually — a real ritual, with real discipline, and it produced genuinely good data. It is also the most tedious thing I do. Every snapshot day starts by logging into fifteen-plus institutions one at a time and typing what each one says into a cell.
The typing isn't even the worst part. The worst part is the reconciliation. The workbook computes a single difference across the liability accounts, so when that number came out wrong, all it told me was that something, somewhere was off — and then I got to go find it by hand, account by account. It could tell me I was wrong. It could never tell me where.
Everything else was bolted on. Collectibles, crypto, and NFTs each got their own sub-ledger, wired into the balance sheet with formulas that drifted out of agreement with their own source data the moment either side was edited. And the history — the part I actually care about — was trapped: the trendline preserves a daily net-worth value going back to 2021, but there is no way to ask it a question. You cannot query a spreadsheet about which account did that, on that day, and why.
wealthVault is that ritual, rebuilt as software that can answer the question the spreadsheet couldn't.
Every dollar of balance change must be explained. If an account was worth $10,100 and is now worth $9,500, that $600 has to be accounted for — by transactions that add up to it, or by an explicit, recorded market adjustment saying "this moved because the market moved." Anything left over is a residual, and a residual is a question the app makes you answer rather than a rounding error it quietly absorbs. That single rule is the difference between this and a budgeting app, and everything below is in service of it.
Snapshot entry that knows what day it is. Balances are entered against a date, not against "now." The calendar marks which days actually have recorded data, and each account shows whether the number in front of you is one you recorded or one carried forward from the last day you did — so a value you never typed can never quietly masquerade as a value you did.
Transactions that understand transfers. Moving money between two of your own accounts is not income and not an expense, and treating it as either is how a ledger starts lying to you. Transfers are first-class and paired: both legs, matched, as one fact. A mortgage payment splits itself into principal and interest, and money moving into a retirement account is recognized as a contribution rather than a disappearance.
Reconciliation that names the account. This is the one the spreadsheet could never do. Instead of a single aggregate difference, every account reconciles on its own: here is the balance change, here are the transactions contributing to it, here is what they fail to explain. For a holding that moves because the market moved, one button records that drift as an explicit adjustment — so it is explained rather than ignored.
A net-worth trendline you can interrogate. The thing the spreadsheet had and couldn't answer questions about. Range presets, a filter by owner, and a stacked decomposition that shows which classes of asset and liability actually moved — plus a 4% sidebar that turns today's number into what it would pay out annually, which is the only reason any of this is worth tracking.
Also, in less detail:
Currently building: the importer that finally lifts seven years of spreadsheet into the database — a two-phase one, which does a full dry run and hands back an audit report before it is allowed to write anything. Its acceptance gate is unforgiving on purpose: the imported history has to match the workbook cell for cell, within a cent. Until it clears that bar, I am still keeping the spreadsheet. Replacing it is the whole point, and I don't get to claim it early.
These are screenshots of the running app, captured against a database seeded with a household I made up. Every institution, balance, transaction, and total below is fictional — none of it is mine, and none of it is anyone's. The four screens are in the order the ritual runs: record the balances, enter what happened, reconcile the difference, then look at the line.
The repo is private, and for this one the reason is not subtle: it is the app that knows what I am worth. Even with the real numbers living in a database rather than the source tree, a personal-finance repo's history is not something I am going to open up casually. The screenshots above are the honest answer instead — the real app, filled with a household that does not exist.
Changes go through the same pipeline as the rest of the fleet — planned, built, reviewed by two independent AI reviewers, then opened as a pull request where an automated reviewer takes its own pass. That process is a project in its own right, and it has its own page.
The guard that couldn't fire, and the reviewer that cared. A function had a defensive check in it that could never trip — the condition it tested for was impossible by construction. Both of my AI reviewers looked at it and said the same reasonable thing: harmless dead code, leave it. The automated reviewer on the pull request looked at the same line and called it a data-integrity hazard, and it was right. A guard that cannot fire is not harmless; it is a claim that someone checked, sitting exactly where a real check should be, quietly discouraging anyone from adding one. Dead code is not neutral when it is shaped like a safety net. Delete it, or make it mean something — and for anything guarding data integrity, make it fail loudly.
The mapper that was correct in isolation and wrong at the seam. I wrote the piece that turns spreadsheet rows into database records. It was pure, it was thoroughly unit-tested, and it was correct: every record it produced was exactly what the specification said it should produce. Several kinds of them would have been thrown straight back out by the database, because the target table's constraints ruled out values the mapper considered perfectly legal — a transfer with only one leg and nothing to pair it to, a transfer carrying a spending category it had no business having, a not-applicable row turned into an expense that was neither a need nor a want. The tests never saw any of it, because they checked what the mapper emitted rather than whether anything downstream would accept it. Code can be provably right on its own and still be wrong at the seam where it meets something with its own opinions. Now I read the constraints of the thing I'm writing into before I write the thing that writes to it, rather than letting the commit be where I find them out.