A personal finance app built on Next.js 16 (App Router) and Supabase.
Its one organising idea: accounts hold money, goals reserve it. Assigning $200 to a goal does not create $200 — it marks money you already have as spoken for. The app keeps two separate ledgers so that distinction cannot be lost:
| Ledger | Table | Answers |
|---|---|---|
| Account postings | AccountPosting |
Where does my money physically sit? |
| Goal postings | GoalPosting |
What is my money assigned toward? |
Balances and goal progress are never stored. Both are sums over their ledger, computed in Postgres.
npm installcp .env.example .env.localFill in NEXT_PUBLIC_SUPABASE_URL and NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY
from Supabase → Project Settings → API. Without them the app renders a setup
screen instead of crashing.
The service-role key is not used anywhere in this codebase and must not be added.
Paste supabase/verify-schema.sql into the Supabase SQL editor and run it. It is
read-only.
Query 1 matters most: it prints the enum labels verified against the linked live project. Compare the output against:
account_type CHECKING, SAVINGS, CASH, BROKERAGE, OTHER
income_source_type JOB, INTERNSHIP, FREELANCE, GIFT, OTHER
earning_status PROJECTED, RECEIVED
transaction_type INCOME, EXPENSE, ACCOUNT_TRANSFER, GOAL_CONTRIBUTION,
GOAL_WITHDRAWAL, GOAL_REALLOCATION, CORRECTION
If these differ, stop and reconcile the linked schema deliberately. Do not add
enum labels merely to make the UI compile. Generated types stay in
src/lib/db/database.types.ts; stable application aliases live in
src/lib/db/schema-types.ts.
Apply these in order with supabase db push. They are re-runnable and do not
drop, rename or rewrite user data. Each is explained in
docs/BACKEND-ADDITIONS.md.
| File | What it adds | Required? |
|---|---|---|
0001_align_enums.sql |
Read-only assertion of the verified enum contract | Yes |
0002_archive_columns.sql |
archivedAt on FinancialAccount and Goal |
Yes |
0003_views.sql |
AccountBalance, GoalProgress (security_invoker) |
Yes |
0004_create_transaction.sql |
Atomic transaction + postings RPC | Yes |
0005_receive_earning.sql |
Idempotent "mark earning received" RPC | Yes |
0006_sync_goal_milestones.sql |
Milestone achievedAt stamping rule |
Yes |
0007_indexes_and_constraints.sql |
Indexes + allocation/earning uniqueness constraints | Recommended |
0008_storage_avatars.sql |
avatars bucket + owner-only write policies |
For profile photos |
0009_harden_rls.sql |
One strict ownership policy per user-owned table | Yes |
npx supabase login
npx supabase link --project-ref <your-project-ref>
npm run db:typesThis overwrites src/lib/db/database.types.ts with the real schema, replacing the
hand-authored version.
npm run dev| Command | Does |
|---|---|
npm run dev |
Development server |
npm run build |
Production build |
npm run typecheck |
tsc --noEmit |
npm run lint |
ESLint |
npm test |
Vitest |
npm run assets |
Regenerate brand assets from Lucide |
npm run db:types |
Regenerate DB types from the linked project |
- Import the repository.
- Add
NEXT_PUBLIC_SUPABASE_URLandNEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEYin Settings → Environment Variables (all environments). - Deploy. Framework preset, build command and output are detected automatically.
npm ci && npm run build && npm startRequires Node 20+. src/proxy.ts runs on the Node runtime, so a static export is
not supported.
Authentication → URL Configuration
- Site URL — your production origin, e.g.
https://your-app.vercel.app - Redirect URLs — add both:
https://your-app.vercel.app/auth/confirmhttp://localhost:3000/auth/confirm(for local development)
Email confirmation and password-reset links will not work until these are set.
Authentication → Providers → Email — decide whether "Confirm email" is on. The app handles both: with it on, sign-up routes to a "check your email" screen; with it off, a session is created immediately and the user goes to onboarding. Nothing assumes a session exists after registration.
| Document | Contents |
|---|---|
| docs/ARCHITECTURE.md | Layers, data flow, the money pipeline, security model |
| docs/DESIGN.md | Design research, the glass system, the validated chart palette |
| docs/BACKEND-ADDITIONS.md | Every SQL object added, and why each one is necessary |
| docs/ASSUMPTIONS.md | Assumptions made and features deliberately deferred |
| docs/QA-CHECKLIST.md | Manual integration script, including cross-user isolation |
| docs/ASSETS.md | The icon/asset system and its licence |
- Row Level Security is the final authority. The app never disables it.
- The service-role key is not read anywhere in this codebase.
- Ownership always comes from the verified session (
auth.uid()/requireSession()), never from a client-supplied id. - Every query and mutation re-establishes identity, because Next's proxy does not reliably cover Server Functions.
?next=redirects are validated against open-redirect attacks.- Raw database errors are logged, never shown.