Four things an independent read found, three of them things this release said about itself that were not true. A custom mood tag travelled without its label, its icon, its sort order and its SCALE. The label is the smaller loss: the person who named a factor "Migräne" would have got a key back. The scale is the real one — the inverse flag marks a factor where a high value is bad, and dropping it does not lose a label, it silently reverses the meaning of every rating already recorded against that tag. Custom cycle symptoms lost their label the same way. Both now carry the ciphertext verbatim, like every other encrypted column in a disaster-recovery payload. The plan file claimed, in the present tense, that a guard requires both a payload reader and a restore branch for every carried model. No such guard exists. The commit that introduced the file said so honestly; the file itself said the opposite, and roughly 31 models have neither end. BACKED_UP means "must be", not "is", and both the plan and its test now say that plainly. The wrong sentence is recorded rather than quietly swapped, because it is the same defect the file was written to prevent. The export route still told operators that labs, allergies, illness episodes and workouts could be exported but never restored. The restore route re-creates all of them, and since this release nutrient day totals too. The scope now points at the plan instead of repeating it beside the code, which is how that sentence came to be wrong. And the export card promised "every record you own". It does not, so it no longer says so — six locales.
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/.