Files
HealthLog/src/app
Marc-André Bombeck 609d248252 feat(cycle): choose cervix as the symptothermal secondary symptom
Let a user pick the Sensiplan double-check's second indicator — cervical
mucus (default) or cervix observation — mirroring the published NFP
standard where the temperature primary sign pairs with one chosen
secondary sign.

- `CycleProfile.secondarySymptom` (MUCUS default | CERVIX) drives which
  second sign the engine confirms against. Casual users keep temperature
  + mucus untouched; the choice lives behind the advanced cycle settings.
- `CycleDayLog` gains the three standard cervix signs — position
  (LOW/HIGH), firmness (FIRM/SOFT), opening/os (CLOSED/OPEN) — as nullable
  columns (migration 0157), wired through Zod, the day-log upsert, the
  DTO, the sync feed, backup/restore and OpenAPI. Additive and
  back-compatible: existing profiles backfill MUCUS, existing day-logs
  keep NULL cervix signs, iOS inherits the fields.
- The engine's `confirmSymptothermal` takes the secondary-symptom choice;
  on CERVIX it confirms against `detectCervixPeak` (last fertile-cervix
  day followed by three closed days — the cervix-closure rule) instead of
  the mucus peak. The double-check still requires BOTH the temperature
  shift AND the secondary peak: a single cervix sign never confirms, and
  the still-learning gate and conservative posture are unchanged.

Engine derived clean-room from the Arbeitsgruppe-NFP / Raith-Paula &
Frank-Herrmann standard.
2026-06-14 15:23:26 +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/.