An iPad in a Safari tab laid the app shell out 10 px in from the left edge in portrait. The shell padded that inset, so a strip of page colour ran beside the rail from the logo band to the account row, and the rail's band borders stopped short of the edge. The desktop shell no longer pads the sides. The rail runs to the window's edge and takes the left inset inside its own background, widening by it so its content keeps its 4rem / 16rem; the logo band, the footer group and the account band reach back over the inset so their lines meet the edge. On the right the top bar and main already run to the edge. The phone shell keeps padding both sides. The side insets are read through --safe-area-left and --safe-area-right, so a test can set one. The account band takes the logo band's height (4rem) with the avatar centred in it, instead of 61 to 69 px of padding with more room below the avatar than above; the home-indicator inset stays under the band. tablet-shell.spec.ts sets a 10 px side inset and a 20 px home indicator and checks that the rail owns the window's left column of pixels, that its band lines start at x = 0, that nothing in it sits inside the inset, and that the account band mirrors the logo band. shell-viewport.test.ts holds the routing.
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/.