Files
HealthLog/src/app
Marc-André Bombeck e4880dcdc1 feat: the score says when a pillar stopped counting
A pillar can leave the set behind the number without anybody choosing it:
a window rolls past its floor, a module goes off, a source goes quiet. The
delta already refuses to narrate that as a health event, which stops a
false "you dropped six points" and puts nothing in its place. The level
moves, the panel shows a different number, and the cause is nowhere on the
surface. Under the adaptive minimum this matters more rather than less,
because compositions are smaller now and sit closer to their floors.

The report carries `compositionNotice` when today's set differs from the
last local day the account has a stored score for. Optional and nullable,
the additive shape the score's wire has used six times, so a payload
cached before it existed stays valid. It names what left and what joined
as pillar ids; the surfaces already own the labels, and the reason a
departed pillar stopped counting comes from that pillar's own row in the
same report rather than from a second vocabulary.

Two changes deliberately do not raise it. A method move and a recipe
change each already have their own note, and telling someone twice about
one event teaches them to ignore both.

The key is `health-score:v<version>:composition:<hash of the sorted set>`
and rides the existing dismissed-item ledger, so a dismissal holds until
the set moves again rather than until tomorrow. The read excludes today on
purpose: the row for today is written by the same request that computed
the report, so comparing against the newest row would find today's own
composition on the second load of the same page and the note would vanish
between two views of it.

This gives `HealthScoreRecord` its first production reader. Since it
shipped it has been written on every scored read and consumed by nothing
but backup and restore, which is the write-only shape the contributor
notes call a liability and which nothing in the gate can notice, because
every check proves one end. The round trip is proved against real
Postgres: raise the note, wipe the table, restore it through the admin
route, get the same note back, and hold the dismissal across it.

Rendering is one muted line on the score card and a card of its own on the
settings page, beside the recipe note rather than inside it. Deliberately
not a push.

Four fake Prisma clients gain the new read. They are doubles of a client
the reader really does call now; leaving them short and guarding the call
site instead would have been a shim for a caller that does not exist.
2026-08-30 16:16:57 +02:00
..
2026-03-10 20:14:33 +01:00
2026-03-10 20:14:33 +01:00

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/.