The ledger records what the last attempt did. It has no way to say what is no longer being attempted: a row frozen at `error_transient` for a month reads exactly like one retrying every hour, because nothing anywhere read the age of `lastAttemptAt`. `sync-verdict.ts` derives the missing reading as a pure read-side projection: eight verdicts down one ladder, no writer, no cron, nothing that can desynchronise from the row it reads. `stalled` is the new one, and it deliberately outranks `failing` — an integration nobody is calling is not failing, it is not running. Every entry on `/api/integrations/status` carries the verdict, computed inline from data the route already holds. That placement is the point: a card-level verdict misses a card-less provider by construction, which is how a dead sync stayed invisible. Nightscout joins the envelope so its card can stop fetching its own status. The freshness module gains the workout leg (Strava writes no measurements at all) and a source-parameterised core so the Apple Health route can reuse it. Its per-metric `stale` flag fires only while the provider itself reads healthy: that combination is the dead-pipe signature, and when the whole connection has stopped every metric ages together anyway. MCP reports the verdict beside `connected`, which only ever meant "not explicitly disconnected".
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/.