Files
HealthLog/src/components
Marc-André BombeckandClaude Opus 4.8 6fdbb8d889 feat(cycle): predicted-period notifications + reproductive-data privacy docs
Add two cycle reminders through the existing notification cascade and the
honest self-hosting privacy framing for reproductive-health data.

Notifications:
- New event types CYCLE_PERIOD_SOON (fires ~2 days before the predicted
  next-period start, from the cached prediction) and CYCLE_PERIOD_CONFIRM
  (a gentle "did your period start?" on/after the predicted start while no
  period is logged, within a short grace window). Both default OFF and are
  gated again on cycleTrackingEnabled — opt in twice for a sensitive surface.
- A 15-minute pg-boss cron mirrors the mood-reminder worker: it scans users
  with cycle tracking enabled + predictionEnabled, computes who is in-window
  from the cached forecast, and dispatches one push per event per local day.
  Idempotency anchors on the push_attempts ledger (no new table). The queue
  is declared, registered in allQueues, scheduled, and wired to a boss.work
  handler, with a source-grep guard so it cannot ship unregistered.
- Honours notificationPrefs.cycle.clientManaged (skip the server push when
  the device owns local reminders, with a suppressed annotation, mirroring
  the medication path) and CycleProfile.discreetNotifications (generic
  "HealthLog reminder" body so no cycle event is named on the lock screen).
- Every attempt is recorded in push_attempts via the unchanged dispatcher.

Privacy / disclaimer:
- docs/self-hosting/reproductive-health-data.md — the "no cloud vendor to
  subpoena" framing with the honest residual-threat caveat (your own host,
  provider, backups, and devices remain part of the surface), citing the
  2021 FTC settlement and 2025 verdict as public record only.
- docs/security/cycle-data-threat-model.md — a compact "protects against /
  does not protect against" note, plus README pointers.
- CyclePredictionDisclaimer component (reusing the GLP-1 estimate-disclaimer
  pattern) for every fertility/prediction surface: descriptive, not a
  contraceptive method, not medical advice, never a safe-to-have-unprotected
  -sex claim. 6-locale copy under cycle.disclaimer.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-06 10:00:42 +02:00
..
2026-05-15 22:59:10 +02:00

src/components/

React components. The UI primitives are shadcn/ui (new-york style, zinc base) over Radix + Tailwind 4 + Lucide; everything above the primitives is feature-grouped by surface. Components are server components unless they need a hook, state, or browser API, in which case they open with "use client". Charts use Recharts 3 — replacing the chart library requires explicit approval.

Primitives and shell

  • ui/ — shadcn/ui primitives (button, dialog, sheet, table, …). Generated by the shadcn CLI; keep edits minimal and on-style.
  • layout/ — the app shell: sidebar, topbar, bottom nav.
  • providers.tsx, app-settings-provider.tsx, version-poller.tsx — top-level context providers and the self-healing stale-shell poller.
  • i18n/ — translation context + locale switcher.

Feature groups

  • insights/ — insight status, assessment cards, Coach surface, advisor cards (the largest group).
  • medications/ — medication cards, the schedule wizard, intake timeline, detail history.
  • settings/ — the section components behind each settings page.
  • measurements/, mood/, dashboard/ — entry forms, lists, dashboard tiles.
  • charts/ — Recharts wrappers.
  • monitoring/ — Umami / GlitchTip client bootstrap.
  • gamification/, targets/, comparison/, doctor-report/, clinician/, onboarding/, admin/ — the remaining per-surface groups.

Conventions

Kebab-case filenames; React component exports stay PascalCase regardless. (A handful of PascalCase-filename outliers under medications/ + onboarding/ are pre-existing drift, noted in ../../CLAUDE.md.) Every t() call needs a matching key in messages/en.json — guarded by i18n-call-site-coverage.test.ts. Data reads go through TanStack Query with keys from @/lib/query-keys and unwrap (await res.json()).data. Tests live in __tests__/.