mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-08 04:32:42 +00:00
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/
Application source. HealthLog is a Next.js 16 App Router project (output: "standalone", React 19, TypeScript strict). The path alias @/* resolves to this directory. Everything a request touches lives under here; the layout below follows the request flow rather than alphabetical order.
Request flow
client → src/proxy.ts → src/app/<route>/route.ts → src/lib/api-handler.ts → handler body → src/lib/api-response.ts → client
proxy.ts— the HTTP edge (Next.js 16's renamedmiddleware). Public-path allowlist, demo-mode mutation block, server-side onboarding redirect, worker-only refusal,x-request-id+ CSP nonce generation, every security header.app/— App Router pages (RSC by default) and API routes. Every API route wraps its handler inapiHandler. Seeapp/README.md.lib/— the server/runtime toolkit:apiHandler, auth, crypto, rollups, AI, notifications, pg-boss jobs, integrations, rate limiting. Seelib/README.md.components/— React components (shadcn/ui new-york, kebab-case files). Seecomponents/README.md.
Other directories
hooks/— shared React hooks (use-prefix, kebab-case).types/— shared TypeScript declarations.generated/prisma/— the generated Prisma client (do not edit; regenerated byprisma generate). Imported as@/generated/prisma.instrumentation.ts— Next.js startup hook; boots the pg-boss worker registry and boot-time backfill queues.__tests__/— cross-cutting unit tests; most tests live in__tests__/folders next to the code they cover.
Conventions
See ../CLAUDE.md for the full set. The load-bearing ones: RSC by default ("use client" only when a hook/state/browser API needs it); every API route wraps apiHandler and parses its body with Zod safeParse; userId is always narrowed from session or Bearer, never accepted as a body field; TanStack Query keys come only from src/lib/query-keys/; outbound HTTP goes through src/lib/safe-fetch.ts.