How to use this
Go in order — later stages assume earlier ones. Each stage has topics to learn, free resources to learn them from, a pointer to where the concept already lives in your own app, and a checkpoint you can verify yourself, without anyone grading it. Stages marked Unavoidable are the ones where a mistake has real consequences (a cross-tenant data leak, an auth bypass) — don't skip those even under time pressure. The full skip/skim guidance is at the bottom.
What Is Code, and Living in a Terminal
Get comfortable with the raw tools — files, folders, a terminal, a real editor — that every later stage assumes you already have.
Topics
- What source code, a program, and "running" code actually mean
- The filesystem: paths, absolute vs. relative, extensions
- Terminal basics —
cd,ls,mkdir,pwd, reading error output - Installing and using VS Code — opening a folder, the integrated terminal
- Environment variables and
PATH, conceptually — you'll need this for.envfiles later
Resources
- MDN — Command line crash coursedeveloper.mozilla.org
- freeCodeCamp — Learn the Command Linefreecodecamp.org
- VS Code — Getting Started docscode.visualstudio.com
See it in NanoBooks
- CLAUDE.md → "Folder conventions"Open the repo in VS Code and confirm you can navigate to /apps/api, /apps/web, /packages/db, /scripts by hand — you don't need to understand any of it yet.
From a terminal, cd into the repo, list the top-level folders, then go two levels deep into apps/api/src/routes and list the files there. No errors, no getting lost.
Git and GitHub Fundamentals
Understand version control well enough to make a change, commit it, and open a Pull Request without fear.
Topics
- What a repository, commit, branch, and diff are
git status,add,commit,log,diff- Branching and merging, and why you never commit straight to
main - Remotes —
clone,push,pull,fetch - What a Pull Request is, and why review exists
Resources
- Learn Git Branching — interactive, freelearngitbranching.js.org
- Pro Git, chapters 1–3 — free official bookgit-scm.com
- GitHub Docs — Get starteddocs.github.com
See it in NanoBooks
- docs/first-pr-walkthrough.mdA no-assumptions, repo-specific walkthrough of exactly this workflow, written for someone at exactly your stage.
Clone the repo into a scratch folder, create a branch, edit one line of a .md file, commit it, and push the branch. You don't need to open a real PR yet — just prove the local flow works end to end.
JavaScript Foundations
Learn general-purpose programming using JavaScript, since it underlies both TypeScript and your entire app.
Topics
- Variables (
let/const), primitive types, operators - Functions, arrow functions, scope, closures
- Control flow —
if/else, loops,switch - Arrays and objects —
.map,.filter,.find, destructuring, spread - Async JS — callbacks → Promises →
async/await(nearly every DB/API call in this app is async) - Modules —
import/export
Resources
- Eloquent JavaScript, ch. 1–11 — free online bookeloquentjavascript.net
- MDN — JavaScript Guidedeveloper.mozilla.org
- The Odin Project — Foundations coursetheodinproject.com
See it in NanoBooks
- apps/api/src/lib/gst.tsSkim it — notice it's "just" functions, objects, if/else, and arithmetic. The scary-looking app is built from the same primitives you're learning now.
Write a standalone script (node myscript.js) that takes an array of objects like { name, amount }, computes a total with .reduce, prints a formatted summary, and includes at least one async function you await.
TypeScript Specifically
Learn the type system this entire codebase is written in — in strict mode, everywhere.
Topics
- Why types exist — catching mistakes before running code
- Basic types,
interfacevs.type, optional (?) and nullable (| null) fields - Typed function signatures
- Union types and narrowing (
typeofchecks) — used constantly for this app's "two JWT shapes" - Basic generics —
Array<T>, enough to readapiFetch<T>() - What
strict: trueenforces, and why it's your safety net as a solo maintainer
Resources
See it in NanoBooks
- apps/api/src/plugins/tenant.tsRead the AuthPayload type usage and the `declare module 'fastify'` block.
- apps/web/src/lib/api.tsThe tenant-id lookup narrows a `string | null` before using it.
Take your Stage 2 script, type every function signature and object shape, turn on strict: true in a tsconfig.json, and get tsc --noEmit to pass with zero errors.
SQL and Relational Database Fundamentals
Understand relational data — tables, rows, joins, keys — independent of any specific database product.
Topics
- Tables, rows, columns, primary keys, foreign keys
SELECT/WHERE/ORDER BY/LIMIT,INSERT/UPDATE/DELETEJOINtypes (inner, left) — this app joins customers ↔ invoices ↔ invoice lines constantly- Aggregation —
GROUP BY,COUNT/SUM/AVG - Why normalization exists
Resources
See it in NanoBooks
- packages/db/migrations/004_create_customers.sqlA real, small, readable table definition from your own app.
Using SQLBolt's or PGExercises' browser-based practice database, write a query that joins two tables and aggregates the result (e.g. "total per category") and get the expected output.
PostgreSQL Specifically
Move from generic SQL to the specific database engine that runs this app.
Topics
- Installing Postgres locally (or the Supabase local stack this repo uses) and using
psql - Types you'll see constantly —
uuid,timestamptz,jsonb,numeric - Transactions —
BEGIN/COMMIT/ROLLBACK, and why they matter - Indexes, conceptually — why some queries are fast and some slow
- Migrations as versioned, ordered schema-change files instead of hand-editing a live database
Resources
- PostgreSQL Docs — Data Definition & Transactionspostgresql.org
- PGExercises — free, runs against real Postgrespgexercises.com
See it in NanoBooks
- docs/local-setup.mdHow this repo's local Postgres actually starts — `supabase start`, `pnpm migrate:up`.
- packages/db/migrations/*.sql151 files — browse a handful in numeric order to see a schema's history unfold.
Follow docs/local-setup.md to get a local Postgres running with all migrations applied, connect with psql (or Supabase Studio at localhost:54323), and run SELECT * FROM tenants; against schema you didn't write.
This App's Multi-Tenancy Pattern Unavoidable
Understand the exact mechanism that keeps one customer's data from leaking into another's — the single highest-consequence thing to get wrong here.
Topics
- The multi-tenant problem — many customers, one database, one set of tables
- Postgres Row-Level Security —
ENABLE ROW LEVEL SECURITY,CREATE POLICY,USING/WITH CHECK - Session-local settings —
set_config('app.current_tenant', ..., true)and why the transaction-scoped flag matters - Why RLS must be the enforcement layer, never an app-code
WHERE tenant_id = ...alone FORCE ROW LEVEL SECURITY, and why even table owners need it
Resources
- PostgreSQL Docs — Row Security Policiespostgresql.org
- Supabase — Row Level Security guidesupabase.com
See it in NanoBooks
- CLAUDE.md → "Multi-tenancy — NON-NEGOTIABLE RULES"Read this section word for word.
- apps/api/src/plugins/tenant.ts`withTenant()` is the entire mechanism in ~6 lines: open a transaction, then `SELECT set_config('app.current_tenant', tenantId, true)` before any query runs.
In your local Postgres, set app.current_tenant to tenant A's UUID in one psql session and try to SELECT a row belonging to tenant B — confirm nothing returns. Then set it to tenant B's own UUID and confirm the row appears. Prove the mechanism to yourself experimentally.
HTTP and REST Fundamentals
Understand how a browser and a server talk to each other, since Fastify and every frontend fetch call are built on this.
Topics
- Request/response cycle, URLs, methods (GET/POST/PATCH/DELETE)
- Status codes — 200s, 400s, and especially 401/403
- What a REST API is — resources, verbs, JSON payloads
Authorization: Bearer <token>headers
Resources
See it in NanoBooks
- apps/web/src/lib/api.tsRead `getToken()` and the fetch call attaching the Authorization header, plus the 401 → redirect-to-login handling.
Use curl to make a raw GET request to any free public API and correctly interpret the status code and JSON body — no browser involved.
Fastify Fundamentals
Learn the backend web framework this app's API is built on, in isolation from this app's own complexity.
Topics
- Routes, handlers,
request/reply - Plugins and encapsulation — Fastify's core architectural idea
preHandlerhooks — functions that run before a handler, how auth checks attach- Decorators (
app.decorate(...)) — attaching custom functionality to the app instance
Resources
See it in NanoBooks
- apps/api/src/plugins/tenant.tsFocus on the Fastify mechanics this time: `fp(...)`, `app.decorate`, and the `declare module 'fastify'` type augmentation.
Build a tiny standalone Fastify app with two routes, one plugin that decorates the app instance with a helper, and one preHandler hook that can reject a request (e.g. a missing header).
This App's Request Lifecycle & Auth Unavoidable
Understand exactly how a request becomes an authenticated, tenant-scoped, permission-checked, audited database operation, in this specific app.
Topics
- Three separate JWT systems that never cross over — Supabase ES256 (owners), custom HS256 (staff), custom HS256 (storefront)
- Algorithm-based dispatch — deciding which verifier applies by reading the JWT header's
alg - Why authorization is re-checked against the database on every request, never trusted from the JWT
- The full mutating-request chain — auth → tenant context → permissions → plan enforcement → audit log
- Feature flags as per-tenant on/off switches
Resources
- JWT.io — Introduction to JSON Web Tokensjwt.io
- @fastify/jwt — official plugin docsgithub.com/fastify
See it in NanoBooks
- apps/api/src/plugins/tenant.tsThe full `requireAuth` function — it decodes-without-verifying only to read `alg`, then dispatches to the right verifier.
- apps/api/src/plugins/permissions.ts, plan-enforcement.ts, audit.ts, feature-flags.tsSkim each — one job per plugin.
- docs/adding-features.mdLists this middleware chain as non-negotiable for any new route.
Pick one GET handler in apps/api/src/routes/invoices.ts and write out, by reading code only, the exact sequence from "request arrives" to "SQL query runs" — including which JWT type it expects and where withTenant gets called.
Zod — Runtime Validation
Learn why TypeScript's compile-time types aren't enough at a network boundary, and how this app validates every request.
Topics
- The gap TypeScript can't close — data arriving over HTTP has no compile-time guarantee
- Defining a schema (
z.object({...})),.optional()vs..nullable() .parse()vs..safeParse(), and what happens on failure- Inferring a TypeScript type from a schema —
z.infer<typeof Schema>
Resources
See it in NanoBooks
- apps/api/src/routes/customers.tsCompare a `.optional()` field ("omit to leave alone") against a `.nullable()` one ("send null to clear it") — a deliberate, load-bearing convention here.
Write a Zod schema for { name: string, email?: string, phone: string | null } and three .safeParse() calls — one valid, one missing a required field, one with a wrong type — printing the outcome each time.
Backend Testing, and This App's node:test Pattern
Learn automated testing generally, then this app's specific approach using Node's built-in test runner against a real database.
Topics
- Why automated tests exist — a program that checks your program
- Unit tests (pure functions) vs. integration tests (real HTTP + real DB)
- Node's built-in runner —
test(),before/after,assert - This app's pattern —
app.inject()(in-process fake HTTP) against a real Postgres + real Supabase auth user - Test isolation and cleanup — a uniquely-named tenant per run, then torn down
Resources
See it in NanoBooks
- apps/api/src/routes/invoices.itest.tsRead the `createShop()` helper (real tenant, real Supabase auth user, real signed-in token), then a `test(...)` block using `app.inject()`.
- apps/api/package.json → "test" script`tsx --test "src/**/*.test.ts"` — confirms no Jest/Vitest anywhere.
Run the existing suite locally (pnpm test in apps/api) and get it green. Then write one new pure-unit test for computeGst() in lib/gst.ts covering the intra-state case, and get it passing.
HTML and CSS Fundamentals
Learn the two building blocks of every web page before touching React or Tailwind, both built on top of these.
Topics
- HTML — semantic elements, forms, attributes
- CSS — selectors, the box model, flexbox and grid for layout
- Responsive design basics — the app must work on a kirana shop owner's small phone
Resources
- MDN — Learn web development (HTML & CSS)developer.mozilla.org
- freeCodeCamp — Responsive Web Designfreecodecamp.org
See it in NanoBooks
- Browser DevTools → any page of the running appInspect the raw HTML Next.js renders for the login page — it's still just HTML and CSS underneath.
Build one static HTML page with a form (name, phone, amount) styled with plain CSS flexbox, usable at both a narrow phone width and a desktop width. No JavaScript needed yet.
React Fundamentals
Learn the UI library the entire frontend is built with, independent of Next.js specifics.
Topics
- Components, JSX, props
useStateanduseEffect— the only state primitives this app uses- Conditional rendering, rendering lists with keys
- Event handling, controlled form inputs
- "Data down, events up"
Resources
See it in NanoBooks
- apps/web/src/components/*.tsxPick any small, self-contained component (e.g. DatePicker, QrCode) and read it end to end — props in, JSX out, maybe one useState.
Build a small standalone React component that renders a mock array of items and has a text input that filters the list live via useState — no routing, no styling library.
Next.js (App Router) Specifically
Learn the specific React framework and file-based routing convention that structures this entire frontend.
Topics
- The App Router —
page.tsx,layout.tsx, nested routing via folders - Route groups — parenthesized folders that organize without touching the URL
- Server vs. Client Components (
'use client') - Dynamic routes (
[param]folders), public vs. authenticated routes
Resources
See it in NanoBooks
- apps/web/src/app/Four route groups — (auth), (dashboard), (marketing) — plus the public `l/[tenantId]` dynamic route used for QR lead capture.
In a fresh create-next-app scratch project (App Router), build two route groups, one dynamic route with a param, and a Client Component with useState.
Tailwind CSS
Learn the utility-class styling approach this app uses everywhere instead of a component library.
Topics
- Utility-first CSS — small single-purpose classes instead of custom stylesheets
- Responsive prefixes (
sm:,md:), state variants (hover:,disabled:) - Why "no component library" means every button and card here is hand-built
Resources
See it in NanoBooks
- apps/web/src/app/(dashboard)/dashboard/**/page.tsxRaw `className="flex items-center gap-2 rounded-lg border p-4 ..."` directly in the JSX — no separate CSS file.
Re-style your Stage 12 form purely with Tailwind utility classes (via the Play CDN or a fresh project) to be responsive, with no custom CSS at all.
This App's Frontend Patterns — apiFetch, i18n, Offline
Learn the conventions this app layers on top of React/Next.js/Tailwind so you can safely extend any dashboard page.
Topics
- A hand-rolled
apiFetch<T>()instead of React Query/SWR — token attach, base-URL detection, throw-on-error, 401 redirect - Flat-dictionary i18n with a
satisfies Dicttrick — a missing key becomes a compile error, not a silent blank - An installable PWA — build-ID-stamped service worker (network-first, GET-only)
- An IndexedDB offline queue for exactly two flows — invoice creation, POS sales — each with a client-generated idempotency ID
Resources
- MDN — Progressive web appsdeveloper.mozilla.org
- MDN — Using IndexedDBdeveloper.mozilla.org
- idb — the Promise wrapper this app usesgithub.com/jakearchibald
See it in NanoBooks
- apps/web/src/lib/api.ts, lib/i18n/en.ts + ta.ts + i18n.test.ts, public/sw.jsThe full apiFetch implementation, the i18n drift-check test, and the __BUILD_ID__ cache-busting story (a real bug this app already hit and fixed).
Add one new key to en.ts and ta.ts, use it via useT() anywhere, and confirm the i18n test passes. Then delete it from only one language file and confirm the test now correctly fails.
Domain Knowledge — Indian GST Basics
Learn enough Indian tax law to read lib/gst.ts and understand exactly why it's review-gated.
Topics
- What GST is, conceptually — not exam-level
- CGST vs. SGST vs. IGST — intra-state splits, inter-state is full IGST
- GSTIN structure — the first two digits encode the state code
- B2B vs. B2C invoices, and why "place of supply" rules exist
- Why a no-GSTIN, no-structured-address walk-in customer is a genuinely hard case, not an oversight
Resources
- GST Portal — official taxpayer guidesgst.gov.in
- ClearTax — plain-language CGST/SGST/IGST guidescleartax.in
See it in NanoBooks
- apps/api/src/lib/gst.tsRead the top comment in full — it cites IGST Act s.10(1)(a), documents a real 2026-08-13 production bug, and names the still-open gap for out-of-state B2C shipments.
- apps/api/src/lib/money.tsAdded 2026-09-19 (PR #72): every GST/discount calculation now converts to integer paise before doing arithmetic, then back to rupees at the end — floating-point rupees can silently drift a paisa the wrong way otherwise. Read the file header for why.
- docs/gst.mdThe written-up rules this file implements.
Without looking at the code, predict the tax split for: (a) TN shop → TN business with a GSTIN, (b) TN shop → Karnataka business with a GSTIN, (c) TN shop → walk-in with no GSTIN. Then read computeGst() and check your predictions.
Domain Knowledge — Double-Entry Bookkeeping
Learn enough accounting to read lib/accounting.ts and lib/accounting-hooks.ts, and why they're review-gated.
Topics
- The accounting equation — Assets = Liabilities + Equity
- Debits and credits — why every journal entry must balance
- The Chart of Accounts — asset/liability/equity/revenue/expense
- Journal entries as the atomic unit of record-keeping
- Auto-posting — a business event silently generating the correct entry
Resources
- AccountingCoach — Debits and Creditsaccountingcoach.com
- AccountingCoach — The Accounting Equationaccountingcoach.com
See it in NanoBooks
- apps/api/src/lib/accounting.tsThe `DEFAULT_ACCOUNTS` array — a small, readable Chart of Accounts for an Indian MSME.
- apps/api/src/lib/accounting-hooks.tsHow a paid invoice turns into an automatic journal entry.
By hand, write the two-line journal entry you'd expect for a ₹1,000 cash sale (which account is debited, which is credited), then find the matching logic in accounting-hooks.ts and confirm it matches.
Docker Fundamentals
Learn containers well enough to understand how this app is packaged for deployment.
Topics
- What a container is, vs. a VM — conceptual level is enough
- Images vs. containers, basic Dockerfile instructions (
FROM,COPY,RUN,CMD) - Multi-stage builds — why this app uses them (smaller, more secure production images)
docker build,docker run,docker-composefor multiple local services
Resources
See it in NanoBooks
- docker-compose.yml, apps/api/Dockerfile, apps/web/DockerfileThe web Dockerfile is directly referenced by sw.js's __BUILD_ID__ comment.
Run docker-compose up locally per this repo's dev instructions and explain, in your own words, what each service in docker-compose.yml is doing.
Cloud / GCP Fundamentals
Learn the specific Google Cloud services this app is actually deployed on.
Topics
- Cloud Run — serverless containers, scaling, the URL you get for free
- Cloud SQL — a managed Postgres instance
- Secret Manager — why secrets never live in code or committed
.envfiles - IAM basics — service accounts, roles, least-privilege
- Artifact Registry — where built images live before Cloud Run pulls them
Resources
- Cloud Run — Quickstartscloud.google.com
- Cloud SQL for PostgreSQLcloud.google.com
- Secret Manager Docscloud.google.com
- IAM Overviewcloud.google.com
See it in NanoBooks
- docs/deployment-gcp.mdThe canonical, repo-specific walkthrough of exactly these services (supersedes the older Railway/Vercel plan in docs/deployment.md).
Read docs/deployment-gcp.md fully and draw, on paper, a box diagram of the six pieces (Cloud Run api, Cloud Run web, Cloud SQL, Secret Manager, Cloud Tasks, Supabase Cloud) with one arrow per interaction.
CI/CD Fundamentals
Understand automated build/test/deploy pipelines in general before reading this app's own.
Topics
- What CI and CD actually mean
- Why tests should run automatically on every push, not just manually
- GitHub Actions basics — workflows, jobs, steps, triggers
- Environments (staging vs. prod) and gated/manual deploys as a safety valve
Resources
See it in NanoBooks
- .github/workflows/deploy.ymlAuto-deploys to staging on push to main; production is a manual workflow_dispatch — a deliberate gate.
Read deploy.yml and answer, from the file alone: what triggers a staging deploy, what triggers a prod deploy, and which step runs migrations. (The honest answer to the third is "none" — find the exact place that proves it.)
This App's Deploy Pipeline Unavoidable if you deploy
Learn the exact tool you'll run by hand, and its documented failure modes.
Topics
- Why this app wraps GCP's REST APIs directly in Python instead of the
gcloudCLI (which hangs in the sandbox) — a real, documented workaround - The pipeline as separate, explicit stages —
migrate,build,release,verify - Why migrations are never automatic — always a distinct, deliberate human step
- The staging-before-prod discipline this repo enforces
- Where deploy credentials live (
.env.deploy) and why they're never committed
Resources
- No generic resource fits — this is entirely repo-specific tooling. Rely on the docs below plus Stages 19–21.
See it in NanoBooks
- scripts/deploy.pyRead the module docstring in full — it names the gcloud/urllib-hangs workaround and the full command list.
- docs/deploy-script.md, docs/product-lifecycle.mdDay-to-day usage and the mandatory release lifecycle.
- .agents/rules/verification-and-known-pitfalls.mdSpecifically: a script printing "✅ passed" is not the same as the check actually passing.
Run python3 scripts/deploy.py env locally and confirm it correctly reports your .env.deploy status. Then explain, in your own words, why running deploy.py all --migrate straight against production without testing on staging first would violate this repo's own documented discipline.
Capstone — Ship One Real Feature, No AI Help Graduation
Prove you can independently take a small real feature from idea to merged code across the full stack.
Topics (synthesis of Stages 1–22)
- Pick a genuinely small, low-risk feature — explicitly not a REVIEW GATE file
- Write a migration —
tenant_id, RLS,FORCE ROW LEVEL SECURITY, a policy - Add a Zod schema and a route using
requireAuth+withTenant/withTenantAudit - Write at least one integration test with the
createShop()/app.inject()pattern - Build the frontend piece with
apiFetch<T>(), updating bothen.tsandta.ts - Run it all locally, then open a real PR
Resources
- Everything from Stages 0–22 — this stage is where it all gets used at once.
See it in NanoBooks
- docs/adding-features.mdYour literal checklist — follow it top to bottom.
- docs/first-pr-walkthrough.mdFor the git/PR mechanics.
Ship one small feature entirely on your own: a migration that applies locally, an API route with correct Zod/auth/tenant plumbing, at least one new passing integration test, a frontend piece using it, both language dictionaries updated with the i18n test still green, and a real PR opened via gh pr create. If every check passes without asking an AI assistant for help at any step, you've met the goal of this curriculum.
Skip / Skim Guidance
If you're time-constrained, here's what genuinely can wait and what can't.
| Stages | Guidance | Why |
|---|---|---|
| 0–3 | Never skip | Everything downstream assumes this. |
| 4–5 | Never skip | Prerequisite for Stage 6. |
| 6 | Absolutely never skip | A mistake here causes real cross-tenant data leaks. |
| 7–8 | Skimmable if purely frontend-focused for now | Come back before touching any route file. |
| 9 | Never skip | Same severity as Stage 6 — wrong JWT dispatch or permission checks is an auth bypass. |
| 10 | Hard to over-skim | Small surface area, a day or two suffices. |
| 11 | Don't skip long-term; can defer past Stage 14 | You can't safely modify billing/GST/accounting code without tests catching regressions. |
| 12–15 | Skim if you'll never touch the dashboard UI | Otherwise standard depth needed. |
| 16 | Skip only if truly never touching the frontend | Otherwise required once, not deeply. |
| 17 | Never skip if touching invoice/tax code | You can't safely review a GST fix without it. |
| 18 | Never skip if touching accounting.ts | Same reasoning as GST. |
| 19–21 | Skimmable if someone else always deploys | Still helps you read deploy.yml/deploy.py even secondhand. |
| 22 | Never skip if you'll deploy yourself | The gcloud-hangs workaround and migrate-is-separate rule are non-obvious and easy to violate destructively. |
| 23 | Never skip | It's the only real proof any of the above stuck. |
Beyond This Curriculum — the Agent Pool
Added 2026-09-19, after you'd already know everything above.
Once Stages 0–23 are second nature, this repo also has a working pool of specialist
agents (development, staging, testing, ops, onboarding, compliance, growth, firefighter)
wired into Google Antigravity as real dispatchable subagents in .agents/skills/*.md,
with a triage layer at .agents/skills/agy-master-orchestrator/SKILL.md. It's built on
the exact same scripts/*.py tools and staging-first discipline this curriculum
teaches — not a shortcut around understanding the stack, a way to move faster once you
do. AGENTS.md is the entry point.