Skip to content

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
ARCHITECTURELEDGERONE · PRODUCTVERIFYCOMMITAuthGoogle · 8h sessionsBrowsersame-origin JSONRoute handlersession + originOwnershipowner from sessionTransactionchange + auditPostgreSQLappend-only triggers

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.

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

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

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

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

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