Solo build · May to September 2026 · Built for my own household

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.

Personal Build PWA Closed Circle Solo Build React + Hono

The short
version.

At a glance
RoleSolo PM and builder.
WhenMay to September 2026. Six weeks from an empty repository to the first live version.
UsersOne household, no external users. That is by design.
What shippedA 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.
Result2,114 of 2,114 tests passing. Zero axe violations across production screens.
LimitThe AI counsel is one disclosed exception: it sends decrypted holdings to Anthropic through the server for that single request.
See itLive product ↗   GitHub ↗
Documentation
GitHub → Live Product →
ONBOARDING TO DASHBOARD: USER FLOW Step 1 “Let’s start with your family.” Household name Step 2 “Who are we planning for?” Members + DOB + risk Step 3 “What do you currently hold?” First holding recorded “See my plan” Dashboard Household health card Allocation donut One nudge Optional fields (units, SIP, maturity date, nominee, notes) stay hidden until asked for. Every step can be skipped and finished later.
WIREFRAME: DASHBOARD LAYOUT YOUR HOUSEHOLD Your plan HOUSEHOLD HEALTH On Track 3 of 5 checks complete. Add a nominee to your largest holding next. WHERE YOUR MONEY LIVES ● Equity ● Debt ● Gold ● Real Estate Total recorded value shown below the donut. ONE NEXT STEP Flag your emergency fund None of your holdings is marked as the household’s emergency fund yet. Review holdings View your holdings Explore what you can invest in Manage your protection cover
SYSTEM ARCHITECTURE: REQUEST PATH Browser Installable PWA Service worker precaches the library and /why only HTTPS + Clerk JWT Hono API Vercel Functions Verifies JWT against Clerk’s JWKS each call household_id Drizzle ORM Typed queries Multi-tenancy enforced in the app layer Neon Postgres Serverless 8 tables No row-level security Row values are encrypted in the browser before they reach the API. See Decisions, D-014. The database sees row counts and timestamps. It never sees the amounts.

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.

Save as PDF →

Project overview

Overview

Vittam 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.

2,114 / 2,114 Tests passing
0 Axe violations across production screens
₹0 Per month to run
6 weeks Empty repo to first live version

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.

1

Name the household

One field. “Let’s start with your family.” A household name, like “The Sharma Household.”

2

Add each member

Name, relationship (self, spouse, child, parent, other), date of birth for age-based milestones, and an optional risk appetite.

3

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.

#CheckWhat it looks for
1Every member holds somethingEach household member has at least one recorded holding.
2Emergency fund flaggedAt least one holding is marked as the household’s emergency fund.
3Parents are protectedEvery member in a parent relationship has active protection cover.
4Spread across classesHoldings span at least 3 of the 6 asset classes.
5Values are currentEvery holding has a current value recorded, nothing stale.
Getting Started 0 to 1 checks complete
On Track 2 to 3 checks complete
Strong 4 to 5 checks complete

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
LayerTechnologyDetails
FrontendVite · React 18 · TypeScriptTailwind CSS + shadcn components, Recharts for the allocation donut, React Router, an installable PWA with a service worker.
APIHono on Vercel FunctionsVerifies a Clerk JWT against Clerk’s JWKS on every request. Resolves household_id from the session, not from a request parameter.
AuthClerkHandles sign-up and login, including Google sign-in, which is why the encryption key is derived from a separate passphrase.
DataDrizzle ORM · Neon Postgres12 tables. Multi-tenancy is enforced in the app layer, not with Postgres row-level security.
AIClaude Sonnet 5Structured 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.
ObservabilityPostHog · SentryProduct analytics and error tracking, both instrumented before launch, both still waiting on real traffic.

How it shipped

11 Vertical slices to reach the v1 core loop
13 days To first deploy, inside a 30-day self-imposed kill criterion
6 weeks Empty repo to feature-complete v1, 2026-07-21
₹0 / mo Infrastructure cost, all providers on free tiers

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.