WorthOS
ActiveAn educational personal-finance organizer — explicitly never an advisor. Organize income, expenses, debts, and goals, see household-finance metrics, and generate a guardrailed AI-review prompt. Now local-first and encrypted at rest behind a passphrase, with a seeded Monte Carlo forecast that reports an uncertainty band instead of one confident line, five switchable skins, and its own prerendered marketing site.
- Personal finance
- Education
- Privacy
- Local-first
- Encryption
- PWA
Updated 2026-08-31

Overview
WorthOS is an educational personal-finance organizer — explicitly not a financial advisor. "Know what you're worth." A user manually enters their financial picture (income, expenses, savings, debts, investments, goals, assets) and gets back a live dashboard of household-finance metrics, cash-flow forecasts, and a debt-payoff projection — plus a copy-ready, guardrail-baked prompt they can paste into any AI chat tool for an educational review, never advice.
It started life as a LAN-only prototype called Money Map AI. It's now a live public PWA at worthos.corycopeland.dev, with a self-hosted cloud sync tier in progress and a $29 offline Starter Kit selling on Gumroad.
The work since has gone to making the privacy claim literally true rather than merely accurate — the local store is now encrypted at rest behind a passphrase — and to making the forecast honest about how little anyone can know about the future.
The problem
Personal-finance tools mostly sit at two extremes:
- Connected-account trackers (Mint-style, or a real advisor app) want your bank credentials and, eventually, want to sell you a product.
- A blank spreadsheet gives you total control and zero structure — most people never finish filling it in, let alone keep it updated.
There's no clean middle option for someone who wants an organized picture of their money — without linking accounts, without advice language, without a subscription just to see their own numbers. And increasingly, people ask AI chat tools financial questions with no structure behind the prompt, getting generic answers instead of ones grounded in their actual numbers.
Audience
- Financial beginners overwhelmed by spreadsheets and jargon, who want a clear, organized picture of their money and better questions to bring to a qualified professional.
- People wary of account-linking — WorthOS never asks for bank credentials; every number is typed in by the user and stored locally by default.
- Casual AI-chat users who already ask ChatGPT or Claude about their finances and want that conversation grounded in real numbers instead of vague generalities.
Explicitly not for: DIY investors looking for buy/sell/hold recommendations. WorthOS won't give them one.
What I built
- A manual intake wizard — profile, income, expenses, savings, debts, investments, goals, assets (with an optional link from an asset to the debt securing it).
- A live dashboard — cash flow, savings rate, emergency-fund months, total debt, total investments, and a Total vs. Liquid net-worth split.
- A cash-flow forecast — 6/12/24-month projection with recurring and one-off adjustments, charted.
- A debt overview with an avalanche-vs-snowball payoff projection — explained, never recommended.
- A goal tracker with progress, timelines, and a portfolio scenario projector.
- Monthly review snapshots — a history view with a net-worth-over-time chart and category-trend sparklines.
- A guardrailed AI-review prompt — the user's numbers summarized into a ready-to-paste prompt with the compliance guardrails built in, for use with any AI chat tool.
- PDF export via the browser's print dialog — no PDF library shipped.
- JSON backup/import. Everything free-tier is local-only, in
localStorage— no account, no server round-trip. - A cloud tier (newer, still landing): magic-link auth and offline-first sync via a self-hosted Supabase instance, with a trial banner and subscription scaffolding. Stripe billing itself is designed but not wired yet — right now the cloud tier is live without payment enforcement.
- A second distribution channel: a $29 one-time Starter Kit on Gumroad — a single-file, fully offline HTML build of the same tool, for people who don't want even a local web app talking to a server.
Built since:
- Encryption at rest, opt-in. The local store can be locked behind a passphrase: AES-GCM-256 over a PBKDF2-SHA-256 key, with the whole thing wrapped in a versioned envelope that carries its own KDF parameters so a future migration doesn't have to guess how an old blob was made. Existing plaintext users get a migration prompt rather than a forced conversion, and a corrupt store gets a recovery screen instead of a white page.
- A Monte Carlo forecast with an uncertainty band. The old projection drew one line, which is the most confident possible way to be wrong about a 24-month financial future. The new engine runs a seeded simulation and renders a band, with volatility and savings-rate levers and a stress-test view that compares two scenarios side by side.
- A real marketing site — About, Manual, Privacy, Sync, Compare, and
Pricing, prerendered at build time into
dist/and hydrated on load, so the app shell and the marketing pages ship from one Vite build instead of two deployments. - Five skins — Classic, Ledger, Understory, Sunday Paper, and Journal — each with its own boot screens, plus a substantial accessibility pass: modal focus traps that stand down when another modal sits above them, and a decorative animation stopped from reaching passphrase fields.
Product decisions
- Compliance as architecture, not copy review. A formal banned-wording list (advisor, recommend, buy/sell/hold, guaranteed, "you should," etc.) is enforced by a recurring grep-based checklist across the whole codebase, with exactly two files allowed to contain those words — the disclaimer bar and the AI-prompt builder — and only to forbid them. The compliance posture is a build-time check, not a style guideline someone can forget.
- A hard technical wall between the free/local and paid/cloud products.
The offline Starter Kit build explicitly blanks the
VITE_SUPABASE_*environment variables at build time, so cloud credentials can never leak into the distributable offline HTML — even as a cloud tier shipped for other users, the "your data never leaves your device" promise stays true for kit buyers by construction, not by promise. - Pre-sell before building the thing. The Starter Kit sales page shipped with a disabled CTA before a store link even existed, specifically to validate demand before writing the packaging, docs, or payment integration.
- Entitlements derived server-side, never trusted from client state. The
in-progress cloud paywall's
is_entitled()Postgres function derives access fromtier + stripe_status + trial_endrather than trusting a rawtiercolumn, with every UI-level gate documented explicitly as "UX hints only" — real enforcement lives in Postgres. Notable because this discipline was designed before the billing tier that needs it actually shipped. - Calibrate the KDF to the device, not to a constant. A hard-coded PBKDF2 iteration count is either too slow on a phone or too weak on a laptop. WorthOS times a short probe derivation at setup, extrapolates to a count landing near 750ms on that device, and clamps the result to a floor and ceiling so a pathologically slow or fast probe can't produce an absurd number. The chosen count travels in the envelope, so the same blob still opens on a different machine.
- Encryption is opt-in, and downgrade is guarded. Forcing a passphrase on existing users would lock people out of their own data to satisfy a security posture they never asked for. So plaintext reads still work and migration is a prompt — but the write path explicitly refuses to silently re-save an encrypted store as plaintext, because the failure that actually matters is the quiet one.
- A forecast that shows its own uncertainty. Replacing the single projection line with a simulated band was a product decision before a technical one: a personal-finance tool that cannot give advice should not be projecting false precision either. The seed is fixed so the same inputs produce the same picture twice — a band that shimmered on every re-render would read as noise rather than as range.
- Atomic, symlink-swap deploys. Docker resolves a bind-mounted symlink at
container start time, not per-request, so
deploy.shrestarts (not recreates) the container after flipping acurrentsymlink to a new release — with 5 releases retained on disk for instant rollback.
Technical architecture
- Frontend: React 19 + Vite SPA, hand-written CSS design system (no
Tailwind), local-first via
localStorage(keymoneyMapAI:v0.1, a naming fossil from the pre-rebrand era). React Router now fronts a route manifest whose shipped routes are prerendered intodist/at build time and hydrated rather than discarded on load. - Encryption: WebCrypto only — no crypto dependency shipped.
PBKDF2-SHA-256 derives an AES-GCM-256 key; the ciphertext, IV, salt, and
iteration count travel together in a versioned
worthos-encrypted-envelope. A boot state machine (useEncryptedBoot) classifies the store on load — absent, plaintext, encrypted, or corrupt — and routes to the unlock, migration, or recovery screen accordingly, with cross-tab invalidation so two open tabs can't disagree about lock state. - Deployment: self-hosted on LXC 209 (
deployment-pve) behind Cloudflare Tunnel atworthos.corycopeland.dev. A separate demo instance atworthos-demo.corycopeland.devruns the same build with data that resets on reload. - Cloud tier: a dedicated self-hosted Supabase instance on LXC 217
(
worthos-db.corycopeland.dev) backs magic-link auth and offline-first sync; the Postgres entitlement function is designed, the Stripe checkout flow is not wired yet. - Distribution: the $29 Starter Kit is a single-file offline HTML build
produced by
npm run dist, packaged with plain-English docs (quick start, safe-use guide, an 8-prompt pack, a monthly-review checklist) and sold via Gumroad.
Design and brand
WorthOS started on a dark navy theme and moved to a calm, light, trust-first palette — off-white surfaces, deep-slate text, a desaturated teal accent — re-derived at the token level so every screen cascaded over in one pass. The most recent work has been almost entirely visual polish: soft clay-morph 3D illustrations (generated with fal.ai FLUX.2, WebP-optimized), an animated landing hero, and Manrope as the display typeface — no product-scope change, just a more premium, less spreadsheet-y feel.
Current status
- Live public PWA at worthos.corycopeland.dev — not a LAN prototype. v0.1 through v0.3 shipped: intake, dashboard, forecasting, debt payoff, goal tracking, snapshot history.
- $29 Starter Kit live on Gumroad, with real pre-sell validation before the payment integration was built.
- Cloud foundation live (magic-link auth, offline-first sync via self-hosted Supabase) — but the Stripe billing gate is still "planned, not wired," so cloud sync is currently accessible without payment enforcement. The auto-created trial was extended from 30 to 60 days, which is the honest move while there is nothing to charge against.
- Encryption at rest shipped — opt-in, with plaintext migration and corrupt-store recovery paths covered by tests.
- Monte Carlo forecasting shipped — uncertainty band, volatility and savings-rate levers, and a two-scenario stress-test view.
- Marketing site shipped — six prerendered pages, with the Privacy, Sync, and Manual pages' encryption and export claims pinned by tests, because a privacy page that drifts from the implementation is worse than no privacy page.
What I would do next
- Wire the Stripe billing gate — the entitlements plumbing (
is_entitled(), UI-hint-only gates) is already designed for this, so it's the one clear blocking step before the cloud tier can charge anyone. - Phase 1: in-app AI analysis using the same guardrailed prompt logic, without leaving the app.
- Extend encryption to the sync path. The local store is encrypted at rest and the transport is TLS, but the cloud tier still holds readable rows — end-to-end would make the Privacy page's claim unconditional.
- Monthly email digest, household/couples mode (Cloud+), an advisor-share report, and scenario modeling — the roadmap phases after billing lands.
Proof
- Live site: worthos.corycopeland.dev
- Demo: worthos-demo.corycopeland.dev — same app, fake data, resets on reload.
- The hero image on this page is a live capture of the demo dashboard.