A multi-book, hands-on learning platform. Each book becomes its own course with lessons, quizzes, exercises/clinical cases, assignments, and flashcards. Progress is saved in the browser (localStorage).
Three courses ship in the box:
- The Well-Grounded Rubyist (16 chapters) — a
codebook with a live in-browser Ruby playground and auto-checked coding exercises, powered by ruby.wasm loaded from a CDN at runtime. - Harrison's Principles of Internal Medicine (14 modules, first ~500 pages)
— a
reflectbook with clinical-vignette quizzes and self-assessed cases. - Newman and Carranza's Clinical Periodontology (14 modules, 14th ed.)
— a
reflectbook covering the periodontium, biofilm, pathogenesis, the 2017 staging/grading, diagnosis, surgery, and implantology, with vignette quizzes and clinical cases.
Each person has their own account, and progress (lesson completion, quiz scores, exercise/case status, assignment checklists, notes) is stored per user in Postgres and synced across devices. Auth is intentionally lightweight — no third-party auth service:
- Passwords are hashed with Node's built-in scrypt (random per-user salt).
- Sessions are opaque tokens stored in the
sessionstable and carried in an httpOnly, Secure, SameSite=Lax cookie (revocable server-side). middleware.tsgates every non-public route; unauthenticated visitors are sent to/login.- Tables (
users,sessions,user_progress) are created automatically on first request (ensureSchema()), so there's no migration step to run.
Copy .env.example to .env and fill in:
| Variable | Required | Purpose |
|---|---|---|
DATABASE_URL |
yes* | Postgres connection string (SSL auto-enabled for non-localhost hosts). |
POSTGRES_URL |
yes* | Alternative name — auto-set by Vercel Postgres/Neon. Either one works. |
SIGNUP_CODE |
no | If set, new sign-ups must enter this exact code (invite gate for a small group). |
* Provide exactly one of DATABASE_URL / POSTGRES_URL. Use the pooled
connection string on serverless (Vercel).
On Vercel: add these under Project → Settings → Environment Variables (the
.env file is git-ignored and never deployed). Setting SIGNUP_CODE is
recommended so only people you share the code with can register.
npm install
cp .env.example .env # then set DATABASE_URL
npm run dev # http://localhost:3000 → redirects to /loginBuild for production:
npm run build
npm startThe Ruby playground fetches the ruby.wasm runtime (~25 MB) from jsDelivr the first time you run code, so that feature needs an internet connection. Every other part of the site works fully offline.
middleware.ts Auth gate — redirects unauthenticated users to /login
app/
page.tsx Library (bookshelf)
login/ , register/ Auth pages
api/
register, login, logout Auth endpoints (set/clear session cookie)
me Current-user lookup
progress GET/PUT per-user progress (Postgres)
[bookId]/
layout.tsx Sidebar shell + per-book accent theme
page.tsx Book dashboard
playground/page.tsx Ruby playground (code books only)
flashcards/page.tsx Full flashcard deck
[chapterId]/page.tsx Chapter with Lesson/Quiz/Exercises/Assignment/Cards tabs
components/ Reusable UI (CodeRunner, Quiz, SessionProvider, UserMenu, …)
lib/
types.ts Shared TypeScript types
books.ts Book registry
ruby.ts / harrison.ts / perio.ts Course content (data)
rubyvm.ts ruby.wasm loader + runner
db.ts Postgres pool + schema bootstrap
auth.ts Password hashing (scrypt) + sessions
progress.ts Server-synced per-user progress store (useSyncExternalStore)
- Add a chapter-data module in
lib/(an array ofChapterobjects). - Register the book in
lib/books.tswithpractice: "code"(gives a code runner) orpractice: "reflect"(gives self-assessed cases).
The routes, sidebar, dashboard, and progress tracking pick it up automatically.
TailwindCSS (v3) provides layout and utility classes; the shared design-system
components (cards, code runner, quiz options, flashcards, etc.) are defined as
component classes in app/globals.css using CSS variables, so the per-book
accent color can be swapped at runtime.