"Sign out everywhere" revoked every other refresh token and nothing else. A native login is two rows, and the Bearer check consults only the paired access token, so the other phones kept working until that token expired, up to a day on the default policy. The route also handed destroyOtherSessions a session-kind credential for a Bearer caller, whose session.id is the ApiToken row id; the exception matched nothing and the calling phone revoked its own refresh token. destroyOtherSessions now finds the paired access tokens through the refresh rows and revokes them inside the same transaction, reporting the count, and the route names a Bearer caller by its access-token hash. Programmatic tokens are untouched. rotateRefreshToken marked the consumed row under usedAt: null alone, so a revocation landing between the read and the write was overtaken and a pair minted after the family ended stayed live. The guard now includes revokedAt: null; on a lost race the minted pair is retired and the reason distinguishes a revoked family from a double consume. withIdempotency consulted the grant only at replay time, and only for its existence; a narrow-scoped token was checked only on the delegated arm. Both move into the cell key: a delegated cell carries the grant's id, level and scope fingerprint, a narrow token's cells carry the token, and a request with no live grant neither reads nor claims a cell. Cookie and wildcard own-record cells are keyed byte-for-byte as before. Tests: integration for the paired access tokens (cookie and Bearer caller), a deterministic revoke-mid-rotation race against Postgres, unit coverage for the new cell keys, and the structural guard moved to the new invariant.
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/.