The seven documented statuses are four kinds of thing, so they get four rulings instead of one. Taken and Skipped state a decision about a dose and each maps onto something already recorded here — a row with a take time, and a row marked skipped. That second mapping was checked rather than assumed: skipped is documented as a deliberate skip and held apart from autoMissed, which is the marker for a dose nobody ever acted on, and the compliance re-fold counts a skipped slot in its own column and never as taken. So a deliberate skip arrives as one. Not Interacted, Not Logged and Unspecified are absences rather than statements. Written as taken they would invent a dose; written as skipped they would invent a decision, because not answering a notification is not choosing not to take something; written as pending they would put outstanding doses from years ago in front of someone today. Snoozed describes what happened to a reminder. Notification Not Sent is about the software. None of the five becomes a row. All five are refused, but each under its own name, because "the file says nothing about this dose" and "this row is about a reminder" are different things and a person acts on them differently. Three reasons where there was one, each with its own sentence in six locales. An inactive medication's history imports and the medication stays inactive. Preserving what happened and putting a medication back on the active list are two acts, and only the first belongs to an import.
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/.