LedgerOne
A private personal-finance ledger, built in small reviewed increments. The identity, ownership and audit foundation is in place; the ledger itself is next.
- Role
- Solo: architecture and engineering
- Year
- 2026
- Status
- In progress
- Type
- Product
01Problem
Why it exists
Most personal finance tools keep a mutable running total. That is easy to build and cannot explain a transfer, a loan repayment or a currency conversion after the fact. The alternatives want a bank connection I am not willing to give.
I want a record I can audit myself: balanced, exact, and impossible to quietly rewrite.
02Approach
How I went about it
Treat it like any software that handles money: decide the invariants first, write them down, and gate each step on evidence. The repository carries a product brief, architecture decision records, an accounting contract, a test plan and acceptance notes for every story.
Foundation before features. Identity, ownership and audit were built before a single financial table, so every later write inherits them instead of having them retrofitted.
03Architecture
Decisions that shaped it
Diagram aboveThe write path today: every mutation commits together with its audit event.
01
A modular monolith, on purpose
One Next.js application with the financial domain kept independent of the UI and the ORM. Microservices and full event sourcing were both considered and rejected in the first decision record: more consistency problems, no present need.
02
Narrow admission, server-side sessions
Sign-in is Google only, against an allowlist. Sessions live in the database, expire after a fixed eight hours with no refresh, and are revoked on logout. Private pages authorise on the server and every route handler authorises again on its own.
03
Ownership enforced twice
The owner is always derived from the session and never accepted from the client. The database repeats the rule with composite keys, so a valid identifier from another user still cannot be linked.
04
An audit trail the application cannot edit
A change and its audit event commit in the same transaction; if the audit write fails, the change rolls back. PostgreSQL triggers reject updates, deletes and truncation on the audit table.
05
Tests that never see real credentials
Integration and browser tests run against a separate database whose name must end in _test. The OAuth exchange is substituted only inside the test instance. There is no test-login switch in the running application.
04Build
What it does
- Liveness and readiness endpoints; readiness returns 503 while the database is down and recovers on its own
- Workspace and audit API with strict input contracts
- Accessible application shell with light, dark and system themes
- Playwright runs on desktop and mobile viewports with automated accessibility scans
- Local tooling refuses to run against a remote database
- Application
- Next.js 16 · React 19 · TypeScript · Tailwind CSS 4 · Radix primitives · React Hook Form + Zod
- Data
- PostgreSQL 17 · Prisma 7 · SQL triggers and constraints
- Identity
- Better Auth · Google sign-in · Database sessions
- Quality
- Vitest · Playwright + axe · GitHub Actions · Docker Compose
05Outcome
Where it stands
Where it honestly stands: the platform, shell, sign-in and ownership stories are done and tested. There are no accounts, transactions or reports yet, and nothing is deployed.
The ledger design — immutable journals, integer minor units, corrections by reversal rather than edit — is documented in the repository as proposed, not built. It is the next phase.
Checked against the repository on 11 November 2026