An AI-native finance workspace. Live data, forecasting, and collaboration in one modelling surface, so a finance team spends less time repairing spreadsheets and more time shaping the plan.
Black, white, and one flash of blue — a finance instrument, not a marketing site.
Honest status, because a README that overstates is worse than no README.
| Area | State |
|---|---|
| Landing page | Built. / — the full marketing surface. |
| Design system | Built. Tokens in app/globals.css, taste in docs/design-system.md. |
| Auth | Built. Email + password, magic link, email verification, database sessions. |
| Modelling grid | Built, on live data. Edits persist through a server action and survive a reload. /workspace reproduces designs/modelling-1.jpg — groups, dimensions, sticky columns, inline editing, undo, scenarios, grain switching. |
| Modelling engine | Built, in memory. AST formulas, cell-level evaluation, aggregation rollup and a command bus in lib/model. bun run calc:check asserts the rollup. |
| Modelling persistence | Built. The model lives in Postgres; /workspace queries it and bun run seed writes it. calc:check runs against the seeded model. docs/modelling-plan.md M0. |
| Reconciliation | Built, R0–R6. An 11,258-record synthetic batch with a labelled answer key, ingestion, a deterministic matcher and a scoreboard: 100% precision, 0% false matches, 98.6% match rate. The remaining 1.4% is six links deliberately left for judgement; the adjudication tier and its validation gate are built but have never run against a live model. The review queue and the forward cash position are at /recon — resolving an exception shrinks the unverified band. bun run recon:eval. docs/recon-plan.md. |
| Organisations / roles | Planned. docs/auth-plan.md §3; tasks A3–A6. |
| Learning site | Built. /learning — the lessons written alongside the code. |
learning/path.ts is the live task list and the single record of where the project is.
Requires Bun and a local PostgreSQL 16.
bun install
# 1. A database the app owns, and a role that owns only that database.
createdb magpie_dev
psql -d postgres -c "CREATE ROLE magpie LOGIN PASSWORD 'choose-one' CREATEDB;"
psql -d magpie_dev -c "ALTER SCHEMA public OWNER TO magpie;"
# 2. Environment. Fill in DATABASE_URL and BETTER_AUTH_SECRET at minimum.
cp .env.example .env
# 3. Schema and client.
bun run db:migrate
bun run db:generate
bun run devThen sign up at http://localhost:3000/sign-up.
Mail (magic links, email verification) goes out over Resend and needs RESEND_API_KEY.
Leave it unset and every email is printed to the server log instead of sent, so the
whole flow is walkable on a fresh clone. Sending is from magpie.akkki.tech, the verified
domain — override with MAIL_FROM if you have your own. bun run mail:check proves the
key works and the domain is verified, without sending anything.
| Command | Does |
|---|---|
bun run dev / build / start |
Next.js |
bun run typecheck |
tsc --noEmit |
bun run lint |
ESLint |
bun run db:migrate / db:generate / db:studio |
Prisma |
bun run auth:tables |
Prints the tables Better Auth expects, read from the installed library |
bun run mail:check |
Verifies the Resend key and sending domain without sending |
bun run data:check / board:check / tour:check |
Database, board and demo-tour suites |
bun run seed / seed:database / seed:board |
The model, the Customers table and the reporting board |
bun run calc:check |
Asserts the modelling engine's aggregation rollup against the seeded model |
bun run history:check / agent:check / ops:check |
Command history, the model agent, and the ops agent |
bun run recon:seed / recon:ingest / recon:match |
Generate the synthetic batch, ingest it, run the deterministic matcher |
bun run recon:eval |
Scores a run against the answer key — precision, recall, false-match rate |
bun run recon:agent / recon:ablate |
The adjudication tier, and the ablation that shows which rules earn their place |
recon:eval is the one that matters. It scores the matcher against a truth.json the
matcher has never seen, and a change that moves the false-match rate off zero is wrong
however good the match rate looks.
DEPLOY.md is the runbook for AWS — ECS Fargate behind an ALB, RDS Postgres, secrets in
Secrets Manager. Every step says what it does and why, because the failures here are the
kind that look like a bug in the app and are not.
TypeScript · Next.js 16 (App Router) · React 19 · Tailwind v4 · Prisma 7 on local PostgreSQL · Better Auth · Bun.
No component library and no chart library: the design system is small enough that
components/ui owns its own primitives, and the charts are hand-drawn SVG. Every
dependency added is one more thing that has to agree with the design.
app/
(marketing) page.tsx the landing page
(auth) sign-in · sign-in/link · sign-up · actions.ts
(app) workspace the dashboard
api/auth [...all] every Better Auth endpoint
learning the lessons
components/
app/ the product: rail, topbar, KPI band, variable grid, agent panel
landing/ marketing surfaces
ui/ charts, logo, toasts — shared primitives
auth/ the two forms and their controls
lib/
auth.ts the auth config; source of truth for the auth tables
authz (planned) requireMembership — the tenancy gate
calc/ (planned) the calculation engine
demo/ fixtures, deleted at M0
assets/
diagrams/ generated architecture diagrams + the scripts that draw them
docs/ design-system.md · auth-plan.md
modelling/ brief.md (verbatim product brief) · main.md (the architecture)
designs/ the prototype screens everything is measured against
learning/ lessons + path.ts, the live task list
prisma/ schema + migrations
proxy.ts Next 16 middleware: coarse redirects only, never the security check
- The auth check lives next to the data, never in a layout. On Next 16 a layout does
not stop the page beneath it from running or from shipping its data in the RSC payload.
docs/auth-plan.md§4. - Database sessions, not JWTs. A finance workspace needs instant revocation and role
changes that take effect now.
session.cookieCachestays off for the same reason. - Formulas are stored as ASTs with ID references, never strings. Renaming a variable
must not break sixty formulas.
docs/modelling-plan.md§1.1. - One command bus for humans and agents. Undo, audit, version history, collaboration and AI editing are one mechanism, not five. §1.3.
- AI output is a proposal, not a write. An agent run produces a
PROPOSEDChangeSet a human accepts. §1.4. - Blue is interactive, violet is machine-authored. Buttons and links are blue; formula
pills, sparklines and the assistant are violet.
docs/design-system.md§2.
There is a scripted walkthrough of the whole product in lib/tour.ts — ten steps across the
model, the databases and a board, driven by driver.js over the real app.
bun run seed && bun run seed:database && bun run seed:board
bun run dev
open http://localhost:3000/models?tour=0
The step index lives in the URL, so a take can be restarted from any step by editing the
address bar, and a reload mid-tour resumes where it was. bun run tour:check verifies every
step still points at something that exists — worth running before the camera is on, because
the failure this prevents is a tour dying halfway through a screen you are narrating.