Principles of Pain Science
A learning platform for a clinical research center's chronic pain curriculum. Chapters unlock on watch time and quiz results, and the research team maintains all of the content themselves.
What they needed
The Center wanted more than a static explainer site. They needed a self-paced curriculum where a participant's progress through each chapter means something. A chapter shouldn't unlock because someone scrolled past it. It should unlock once they've watched enough of the chapter video and passed the quiz, and that progress has to be saved per person so the team can report on it later.
Learners choose one of two paths. The certificate path starts with a pre-assessment, unlocks chapters in order as each quiz is passed, and ends with a final exam that needs 80% to earn the certificate. The casual path has no pre-assessment and no locked units, and a chapter counts as done once enough of its video has been watched, so the quiz is optional.
The people writing the curriculum are researchers and clinical staff, not developers. They needed to add and edit every chapter, quiz question, and piece of media themselves, without a deploy and without asking me first.
Architecture
Astro 6 runs in hybrid mode on Cloudflare Workers. Authenticated routes (the dashboard, chapter gating checks, progress endpoints) render on the server per request, and the informational pages around them stay static. Only the pages that need per-user logic pay for server rendering.
Progress lives in Cloudflare D1 through Drizzle ORM. A typed relational schema enforces the gating rules (video threshold met, quiz passed, chapter marked complete) in one place and makes the data queryable, which matters when progress may feed into participant tracking. Access uses Better Auth with a KV-based allowlist, so participation is provisioned per email and there is no open signup. The end-to-end tests use the same allowlist: they seed a known entry and reset a test user before each run, so the auth layer is never mocked.
The curriculum (chapters, quiz content, media) lives in Sanity v3, separate from the auth and progress code so the editing side stays simple. That only works if editors can actually use it, so I wrote a CMS training manual for research staff and a shorter developer guide, then built a NotebookLM notebook on both so the team can ask questions instead of searching a markdown file.
npx wrangler d1 migrations apply pops-db --local --config wrangler.json
npm run test:e2e:kv-allowlist # seed a known allowlist entry
npm run test:e2e:reset-user # reset the e2e test user
npm run test:e2e # Playwright, against a real D1/KV-backed dev server Constraints
The main constraint was testing an auth-and-progress flow without mocking it. Gating touches auth state, D1 data, and KV, so the end-to-end suite needs a repeatable way to reset a known user and re-seed the allowlist before every run. Those steps are npm scripts instead of setup notes. Vitest covers the gating rules at the logic level, and Playwright covers the full path a participant takes.
The other constraint was search. The platform needed on-site search across the curriculum without a separate hosted search service. Pagefind runs as its own step in the build pipeline before the Astro build, so search stays static and adds no runtime dependency.
- Auth-gated chapter progression, enforced server-side
- Typed progress schema via Drizzle + D1
- Editor-owned curriculum, with training docs for non-developers
- Vitest for logic, Playwright for e2e with scripted auth fixtures