A dose can now name a vaccine the catalogue does not list. The person defines it once (a name, the diseases it protects against from the catalogue's antigen list, the series length and the booster interval) and logs doses against it; such a dose counts into each listed antigen's series, settles a booster reminder keyed to one of them and offers a booster, the way a catalogue combination does. One resolver (resolve-vaccine-entry.ts) now answers for every consumer that asked the catalogue directly: the series, the satisfy matcher, the booster mint, the dose DTO, the doctor report, document links, the upload suggestion, MCP search and the client's info sentences and booster offer. The catalogue wins when a dose carries both. The custom routes resolve the record through requireRecordAuth (list at READ, create at WRITE with the actor-keyed write bucket, edit and remove at MANAGE with the C4 overwrite audit) and join the sharing guard, the MANAGE matrix and the activity feed. Removing a definition tombstones it and lets go of its doses, each keeping a name. Definitions travel in the backup and restore before the doses that name them; the dose form picks them, adds one without leaving the form, and the page lists them for editing. With the last contract stub implemented, the 501 helper and the not_implemented code leave the tree.
src/app/
The Next.js App Router tree: the page surfaces a logged-in user sees and the REST API under api/. Server components are the default; "use client" is added only where a hook, state, or browser API needs it. Routing is file-based — a page.tsx is a page, a route.ts is an API endpoint.
API routes (api/)
Every endpoint wraps its handler in apiHandler (@/lib/api-handler), parses any body with Zod safeParse, narrows userId from the session or Bearer token (never from the body), and returns the { data, error, meta? } envelope. requireAdmin() is cookie-only, so api/admin/ is unreachable by Bearer token. Notable groups:
auth/— login, register, password, passkey (WebAuthn), refresh, Codex device-OAuth.measurements/,mood-entries/,medications/,sleep/— the core health-data CRUD + batch/bulk ingest.ingest/— external Bearer-token ingest surfaces.insights/,analytics/,dashboard/— AI insights, the Coach SSE stream, trend summaries, the first-paint dashboard snapshot.withings/,whoop/,fitbit/,integrations/,sync/— OAuth connect/sync/webhook surfaces and the offline delta feed.fhir/,share-links/,doctor-report/,export/,import/— clinician-facing FHIR R4 read API, shareable records, report + data export.notifications/,devices/,send/,telegram/— channel registration, device tokens, dispatch, webhook intake.admin/— operator panel APIs (cookie-only).health/,version/,meta/,internal/— health check, build-version probe, runtime metadata, worker-internal routes.
Page surfaces
measurements/, medications/, mood/, insights/, achievements/, notifications/, settings/, onboarding/, admin/, plus the public about/, privacy/, bugreport/, the share-record route c/, and the root dashboard (page.tsx). Shared shell + chrome lives in layout.tsx; error.tsx / global-error.tsx / not-found.tsx / loading.tsx cover the framework boundaries. layout.tsx holds the one nonce-bound dangerouslySetInnerHTML (theme bootstrap).
Conventions
See ../../CLAUDE.md. The request-flow walk in .planning/codebase/arch.md annotates each layer with file:line citations. Related dirs: handlers compose ../lib/; pages render ../components/.