PUT /api/measurements/[id] was the one write surface that skipped the per-type plausibility bands — POST, batch, CSV import, Apple export, Telegram and MCP all enforce VALUE_RANGES — so an implausible edited value flowed unchecked into rollups, the health score, the BP gates and the Coach snapshot. The route now validates the new value against the row's own type after the lookup and returns the standard multi-issue 422 envelope on violation. Value edits on server-owned sources (anything outside WRITABLE_MEASUREMENT_SOURCES: connector, import, computed rows) are refused with 409 and the stable errorCode measurement.update.server_owned_source — editing a provider-attributed number forges a reading the server never received. Timestamp and note edits stay allowed, and the edit sheet shows a localised message for the new code. The schema's generic min(0).max(500000) bound is dropped: the per-type band is stricter where it matters, and min(0) blocked legitimate edits of the signed types (ANS_CHARGE, BODY_TEMPERATURE_DEVIATION). Route tests cover out-of-range 422, negative signed-type accept, the server-owned 409, a timestamp-only edit on a server-owned row, and the happy path. The PUT endpoint is not part of the published OpenAPI surface — the generated contract is unchanged.
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/.