A subscription command center for people whose software spend has quietly gotten
away from them. Built from the Ledger.dc.html Claude Design canvas — same
layout, same tokens, same interactions — then extended into a working app with
eleven screens, ten demo personas, and a real new-user path.
npm install
npm run dev # http://localhost:5178Judges — use the demo account:
| Username | judge |
| Password | ledger2026 |
Both are printed on the login page with a Fill it in for me button. It's seeded into the database on first run and loads all ten worked personas.
Or create an account. Create account on the login page makes a real user record, gives you your own empty workspace, and drops you at onboarding with a CSV upload waiting. Accounts are isolated — one user cannot see another's workspaces. Sign out and delete-account live in the sidebar and on Plans & billing.
Passwords are salted and hashed with PBKDF2-SHA256, 150,000 iterations, via the Web Crypto API. The plaintext is never stored. But be clear about the limit: with no server this is a demo door, not real security. Anyone with access to the browser profile can read the digest and run offline guesses, and there is no rate limiting, no email verification and no password reset. Real auth needs a backend.
There's no server, so Ledger ships its own database: IndexedDB, the
transactional, indexed store built into every browser. Five object stores, a
versioned schema and owner indexes, all in src/lib/db.ts:
| Store | Key | Holds |
|---|---|---|
users |
username |
Account records — display name, email, salt, PBKDF2 digest |
workspaces |
id (index: owner) |
Workspaces people create |
mutations |
owner::workspaceId (index: owner) |
Every change to a workspace — edits, cancellations, decisions, imports |
prefs |
owner |
Theme and the workspace you were last in |
imports |
id (index: owner) |
CSV import history: file, row counts, timestamp |
The ten demo personas stay compiled into the bundle as read-only seed data —
your changes to them are stored as a mutation layer on top, which is why Reset
this workspace can undo everything without re-downloading anything. State from
the earlier localStorage build migrates into the database automatically on
first load.
Everything is still local to one browser. No sync, no sharing between devices, and clearing site data clears the database.
Import CSV on the Subscriptions screen, or Upload a CSV in onboarding.
- Drag a file in or pick one — it's parsed in the browser and never uploaded.
- Columns are matched by name (vendor, price, billing cycle, renewal, last used, active days, payment method, notes) and you can correct any of them before anything is imported.
- Dates or day counts both work:
2026-09-08,19,in 19 days,today. - A preview shows every row with the price, cycle, renewal and the verdict Ledger derived, plus per-row warnings for anything it had to guess.
- There's a downloadable template and a Try the sample button with eight realistic rows.
The parser is a proper RFC4180-ish implementation — quoted fields, escaped
quotes, embedded newlines, CRLF and BOM all handled (src/lib/csv.ts).
Every item in the left nav is live. Nothing is a placeholder.
| Screen | What it does |
|---|---|
| Command Center | Monthly recurring with the month-over-month story, a stat rail, the Decide today queue, a 60-day renewal runway with hoverable ticks and deal-revert diamonds, spend-by-category, longest-unused vendors, and department seat utilization on teams. |
| Subscriptions | Five tabs (Active, Trials, Metered & API, Deals & credits, Archive), ledger and card views, saved-view chips, search, and multi-select bulk actions. |
| Expenses | One-off charges beside the ledger — filter by type and category, add, delete, export. Business and reimbursable totals tracked separately. |
| Calendar | A month grid of every dated money event: renewals, annual renewals, trial conversions, deal reverts. Click a day to see exactly what hits the card. |
| Usage | Every tool ranked by usage score, with signal confidence, active days, cost per active day and a 12-week sparkline. Sortable four ways. |
| Savings | The recommendation queue, derived from your own ledger. Acting on one actually performs it; open / actioned / dismissed are all tracked. |
| My AI Stack | Your AI tools grouped by the job they do, with overlap detection and a Markdown export. |
| Workspace | Teams only: people and seats, the approval queue with conflict context, shadow-IT discovery, and automation policies. |
| Reports | Month-over-month totals, biggest movers, category breakdown, and CSV / Markdown / print export. |
| Integrations | Connect and disconnect cost sources, usage signals and alert channels. Coverage is scored honestly. |
| Plans & billing | Usage against Free limits, invoices, the four-tier comparison, and a cancel flow that doesn't fight you. |
Plus three overlays: the subscription detail drawer (verdict and why, cost per active day, wasted-since-use, 90-day usage timeline, deal countdown, overlap consolidation, seat roster, billing history), the upgrade sheet, and a ⌘K command palette.
Each is a full workspace — its own ledger, expenses, people, approvals, discovery queue, integrations and category history.
| Persona | Shape | What it exercises |
|---|---|---|
| Maya Alvarez | Solo founder · USD | Deal reverts, trials, zombies, tool overlap, metered burn, prepaid credits |
| Dana Whitfield | Freelance designer · USD | A price that doubled, a shipped project's leftover tooling, lifetime deals |
| Tomás Rivera | Creator studio · USD | Three clipping tools, two analytics tools, a deal reverting this week |
| Kavya Sharma | Solo marketer · INR | Client tools on a personal card, two SEO suites, a silent autopay renewal |
| Arjun Menon | Indie developer · INR | True-cost forex, UPI e-mandate health, local-pricing arbitrage |
| Priya Raghavan | CS student · INR | An expired student pack, two AI assistants, a course at 22% |
| The Osei Household | Family · USD | Shared seats, four profiles, two streaming services nobody opens |
| Northwind AI | Startup team · 24 | Seat reclaim, a pilot that never rolled out, approvals |
| Ridgeline Ventures | Small fund · 9 | Three sourcing tools, $600/mo of idle research seats |
| Helix Labs | Scale-up · 41 | $39K of inference, 19 idle Copilot seats, a four-deep approval queue |
Switch between them in the workspace menu, or with ⌘K.
+ Start a new workspace in the workspace menu creates a genuinely empty one. It gets its own onboarding: add what you already know, connect a cost source, or log a one-off expense. Screens that need data show that path instead of empty charts, and the Command Center drops the month-over-month badge until there is a second month to compare.
Everything a new workspace does is the same code the personas run — adding a subscription scores it, gives it a verdict, puts it on the calendar, and starts generating recommendations immediately.
- Add, edit and delete subscriptions. New rows get a usage score and verdict
from the numbers you enter (
src/lib/verdict.ts); Ledger writes the reason line if you leave the note blank. - Recommendations are derived, not scripted (
src/lib/recommend.ts) — from verdicts, deal reverts, trials, idle seats, price hikes, capability overlaps, local-pricing gaps and duplicate coverage. Which is why they work for a workspace created five minutes ago. - Acting on a recommendation performs it: cancellations open a task, seat reclaims release seats and shrink the paid count, consolidations open the keeper. Decisions persist with a snapshot, so the Actioned list survives the recommendation disappearing from the queue.
- Mark used rewrites a row's signals — confidence drops to self-reported, and a Cancel/Zombie verdict softens to Watch.
- Approvals, discovery and policies are real state: approve, decline, reverse a decision, claim or flag a tool, toggle a policy.
- Integrations connect and disconnect, and coverage feeds the Usage screen's honesty about whether a verdict is evidence or a guess.
- Exports are real downloads — subscriptions CSV, expenses CSV, a Markdown report, and your AI stack as Markdown.
- Everything persists per account, per workspace, in the IndexedDB database.
- CSV import parses in-browser, maps columns, previews with derived verdicts, and records the import in the database.
- Keyboard:
⌘Kpalette,gthenc/s/e/u/v/r/i/pto jump,escto close the top layer. - Repeatable demos:
⌘K→ Reset this workspace to its demo state undoes every change in the current workspace, so a recording can be retaken without reloading. Workspaces you created can be deleted from the workspace switcher.
src/
data/ seed.ts the three design personas, verbatim
personas.ts seven more, hand-authored
extras.ts expenses, people, approvals, discovery, integrations
workspaces.ts composition + the blank new workspace
types.ts
lib/ db.ts IndexedDB schema, transactions, owner queries
crypto.ts PBKDF2-SHA256 password hashing
csv.ts CSV parser, column detection, row → subscription
format.ts money, dates, seeded sparklines
derive.ts row mapping, runway, verdict/confidence maps, CSV out
recommend.ts the savings engine
verdict.ts scoring rules for manually-entered rows
state/ auth.tsx accounts, sign in/up/out, session
store.tsx context store, database-backed per account
components/ eleven screens, four overlays, three modals
styles/ tokens.css (dark + light), app.css
The demo clock is pinned to 20 Aug 2026 (TODAY in src/lib/format.ts) so
every "renews in 12d" lines up with the design's copy. Point it at new Date()
when real data lands.
- Two base currencies. USD and INR, matching the design's forex model (₹88.50 plus a 3.5% markup). Adding more means a rate table, not new UI.
- Seat prices are per seat, per period. Flat platform fees carry no seat count, so idle-seat maths stays honest.
- Downgrade savings are estimated at 40% of the current price, and labelled
with a
≈— the app doesn't know the vendor's tier below. - Vendor hand-offs stop at the boundary. Plan changes, payment fixes and cancellation pages surface an explanatory toast where a real integration would take over; Ledger never claims to have cancelled something on your behalf.
- The database is per-browser. IndexedDB is a real database with real transactions, but it is not a server — no sync across devices, no shared team workspaces, and no recovery if the profile is wiped.
- Imported rows carry inferred confidence. A CSV tells Ledger what you pay, not what you use, so imports never claim a verified usage signal.