See your family’s money
on one page.
Vittam is a financial planning app I built for my own household. Not one account per person. One page for the whole family. Record what everyone holds, see the gaps in the plan, laid out the way a financial planner would.
I did not build this to reach a market. I have no infrastructure budget or compliance setup to serve real households at scale, and I say that plainly rather than let the design imply otherwise. This is a closed-circle build: a real system, built end to end, to show how I think and how I build, not a product I am pitching.
The short
version.
At a glance
| Role | Solo PM and builder. |
| When | May to September 2026. Six weeks from an empty repository to the first live version. |
| Users | One household, no external users. That is by design. |
| What shipped | A household finance PWA. One page for the whole family, encrypted in the browser, with what-if ledgers, an on-demand AI counsel layer and Excel import. |
| Result | 2,114 of 2,114 tests passing. Zero axe violations across production screens. |
| Limit | The AI counsel is one disclosed exception: it sends decrypted holdings to Anthropic through the server for that single request. |
| See it | Live product ↗ GitHub ↗ |
This page is the build record for Vittam. It covers the household finance problem, the onboarding and dashboard, three features added after the first ship, the four decisions that shaped the architecture, and the honest state of a product that is live, used by one household, with no external users.
Project overview
OverviewVittam is a solo build, from an empty repository to a live product in 6 weeks. It is free to use, live at finance.gauravg.dev, with source at github.com/argaur/household-financial-pwa.
These are the numbers I have evidence for today. I do not have usage numbers, and I am not reporting any. Vittam has one household of users: mine. It has no external users. That is by design, not a gap I am waiting to close. I built this to prove out an architecture and a build process, not to win a market.
My money was scattered.
No app showed the whole family.
01 · Problem
My own household holds money in many places. A savings account here. A mutual fund there. Gold in a locker. A policy nobody had read in years.
Each app showed one slice. No app showed the household. I had to hold the whole picture in my head, or on a spreadsheet that went stale the day I stopped updating it.
Three problems, not one
My money was scattered
Bank apps, broker apps, insurance portals, a folder of paper. Nothing talked to anything else.
No household view
Every app I used is built for one person. My family plans as a unit. None of those tools have a concept of a household at all.
Advice that ignores what you own
Generic advice did not know which family insurance policies were already in place. It repeated the same tips regardless of what we already held.
Vittam is built for my own household making a long term plan. It is not a product I am pitching to the wider market, and it is not built for day traders watching charts.
Fragmented finances
are my normal case.
02 · Context
My household is not one person with one portfolio. It is several people, each with their own accounts: a pension, a separate set of mutual funds, a child’s long-term savings scheme, and a fixed deposit that nobody had moved in a decade.
Each of those sat in a different app or a different paper file. Nobody added them up. So questions that sound simple, like “are we covered if something happens to the main earner,” did not have a clear answer.
Vittam starts from the household, not the individual. One page. Every member. Every holding they have told it about. I built it this way because that is how my own family’s money actually sits, not because I researched how other households sit.
Built for my own household.
A closed circle, not a market.
03 · Users
Vittam is built for my own household, and I am stating that plainly rather than letting the design imply a bigger market. There was no user research and no interviews behind this, because there were no other users to interview. I did not build a persona deck. I built it from my own situation: money spread across apps, no single view of the family, and generic advice that did not account for what we already held.
I also have no infrastructure budget or compliance setup to serve real households at scale, and that is not an accident. Manual data entry, client-side encryption, and a bring-your-own-passphrase recovery model are the right choices for a closed circle who trusts me directly. They would be the wrong choices for a product trying to onboard strangers, and I am not trying to.
Onboard the family.
Get one honest number back.
04 · Solution
Onboarding is three steps. Name the household. Add each member. Record what you currently hold. Then Vittam shows a dashboard with one health tier and one next step, never a wall of numbers.
Name the household
One field. “Let’s start with your family.” A household name, like “The Sharma Household.”
Add each member
Name, relationship (self, spouse, child, parent, other), date of birth for age-based milestones, and an optional risk appetite.
Record what you hold
Member, instrument from a 30-instrument library, invested amount, current value. Everything else stays hidden until you ask for it.
The dashboard, and the five checks behind it
The dashboard shows a household health tier, an allocation donut colored by asset class, and exactly one nudge. Never zero nudges, never a list of ten. The tier comes from five checks.
| # | Check | What it looks for |
|---|---|---|
| 1 | Every member holds something | Each household member has at least one recorded holding. |
| 2 | Emergency fund flagged | At least one holding is marked as the household’s emergency fund. |
| 3 | Parents are protected | Every member in a parent relationship has active protection cover. |
| 4 | Spread across classes | Holdings span at least 3 of the 6 asset classes. |
| 5 | Values are current | Every holding has a current value recorded, nothing stale. |
Beyond the dashboard sits a public library of 30 instruments, five in each of
six asset classes: Equity, Debt, Gold, Hybrid & Guaranteed, Real Estate, and
Alternative. Each entry explains typical returns, risk level, minimum
investment, tax treatment, liquidity, and who can invest. No account is needed to
browse it, at /explore. A separate page, /why, lists
the build decisions in public, each one paired with the alternative it beat.
The core loop shipped in 6 weeks.
Three more features came after.
05 · Since Launch
The onboarding-to-dashboard loop above is the v1 product. It has kept growing since. Three features shipped after that first ship gate: strategy ledgers, an AI counsel layer, and bulk holdings import from Excel. All three are live at finance.gauravg.dev today.
Strategy ledgers
A household can hold up to four what-if plans alongside Current, each one a full encrypted snapshot. Editing Current later does not change a saved ledger. A three-number strip shows the delta against Current: total value, equity share, monthly SIP.
AI counsel, on demand
Claude Sonnet 5 reviews a ledger and suggests a different asset mix, through a thin server proxy that forwards the request and writes nothing to the database or the logs. Apply creates a new ledger carrying the suggestion. Dismiss changes nothing. A monthly cap keeps the cost near zero.
Bulk import from Excel
Download a template pre-filled with the household’s member names. Fill it in and upload it back. A review screen sorts every row into Ready, Needs attention, Possible duplicate, or Skipped, and only the Ready rows get committed.
The AI layer needed one narrow exception to the encryption model.
Decrypted plaintext has to reach Anthropic for the suggestion to be
generated. The browser still decrypts and re-encrypts, the database
still never sees plaintext, and the exception is stated on
/privacy, not folded quietly into the existing claim. See
D-017 below.
Four decisions
that shaped the product.
06 · Decisions
D-002: No live prices
Values are typed in by hand. There is no live price feed from mfapi.in, CoinGecko, or a gold API. The dashboard will not match today’s market price. In exchange, there is no external service that can break the app.
The dashboard’s job is to show allocation and completeness, not today’s price. A stale number that is always available beats a live number that sometimes is not.
D-014: Encrypted in the browser
A household’s data is encrypted on their device before it reaches the server. The key comes from a passphrase set separately from login, because Google sign-in has no password to derive a key from.
A recovery code is shown once at signup. It is never stored on the server. Lose the passphrase and the recovery code both, and the data is gone for good, even from a database backup.
I will state this plainly: this stops me, the person who runs the server, from reading a household’s data. It is not a claim that the encryption cannot be broken by someone determined enough. The whole row is encrypted into one column, not field by field, and row sizes are padded so the length of the ciphertext does not leak the size of an amount or a name. The unlocked key lives only as a non-extractable key in the browser’s IndexedDB. The server still sees metadata: row counts, timestamps, activity patterns.
Two side effects followed from this. holdings.instrument_id
stopped being a real foreign key, because the browser now matches it against the
public instrument list instead of the database checking it. And a
version column does double duty, catching replay attempts and
resolving conflicts when the same household edits from two devices.
D-011: The obvious fix that was wrong
A review found that the learn_card_slug analytics property was
polluted with route names for 5 of 6 nudge destinations. The obvious fix was to
add 2 more instruments to the library, so every nudge would point at an
instrument page.
I rejected it, for two reasons. It would only have fixed 1 of the 5 broken cases. And term insurance and an emergency fund are not asset classes. Adding them to the library would have bent the data model just to satisfy a tracking field.
The real fix was a new target_type property, with values
learn_card or route. It described what the nudge
actually pointed to, instead of forcing everything to look like an instrument.
D-017: A narrow, disclosed exception to “encrypted in the browser”
The AI counsel feature needed the household’s decrypted holdings to ask Claude for a suggestion. That is a direct conflict with D-014, which says the server never reads plaintext.
I did not soften the encryption claim to make room for the feature. The browser decrypts, sends plaintext over TLS to a thin server route, that route forwards it to Anthropic and relays the answer back, and writes nothing to the database and nothing to the logs. The server sees plaintext in flight for one request, never at rest.
I also corrected my own earlier wording. An early version of this
decision said the server sees plaintext “never at rest.” That is
true of Vittam’s own infrastructure and false end to end as worded,
because Anthropic’s standard API retains inputs for up to 30 days
absent a zero-retention agreement. The corrected claim, stated on both
/why and /privacy: Vittam’s database does not
store AI-request plaintext. The AI provider may retain it under its own
policy.
The bug that taught the most, B-001. The single dashboard nudge, which is the whole point of the “close the loop” feature, never rendered in production. All 294 tests were green at the time.
The calculation was correct and tested. The card that displays it was correct
and tested. The API route in between quietly left the nudge field
out of its response, and the frontend degraded silently. No crash, no Sentry
error.
I found it by clicking through the app by hand, because sign-up sits behind a Cloudflare Turnstile that blocks automated browsers from reaching that flow. The lesson: covering every unit is not the same as covering the system. Now the seam gets a test written before either side of it.
Eleven slices to start.
Kept shipping after that.
07 · Tech
| Layer | Technology | Details |
|---|---|---|
| Frontend | Vite · React 18 · TypeScript | Tailwind CSS + shadcn components, Recharts for the allocation donut, React Router, an installable PWA with a service worker. |
| API | Hono on Vercel Functions | Verifies a Clerk JWT against Clerk’s JWKS on every request. Resolves household_id from the session, not from a request parameter. |
| Auth | Clerk | Handles sign-up and login, including Google sign-in, which is why the encryption key is derived from a separate passphrase. |
| Data | Drizzle ORM · Neon Postgres | 12 tables. Multi-tenancy is enforced in the app layer, not with Postgres row-level security. |
| AI | Claude Sonnet 5 | Structured outputs through a thin Hono proxy. No prompt caching, a fixed monthly call cap, and no plaintext written to Vittam’s own database or logs. |
| Observability | PostHog · Sentry | Product analytics and error tracking, both instrumented before launch, both still waiting on real traffic. |
How it shipped
First deploy landed 2026-07-10, 13 days into a 30-day rule I set for myself: if the core loop was not live by day 30, I would stop. It shipped early. The v1 product went feature-complete on 2026-07-21, then kept growing through September with the features in the Since Launch section above.