Commit Graph
5 Commits
Author SHA1 Message Date
Marc-André Bombeck a635c1de05 fix(cycle): v1.15.1 QA reconcile — purge, rate limits, marker contrast, token discipline
- Add CYCLE_FERTILE_SOON to the cycle-purge push_attempts eventType list so a
  data reset leaves no dated reproductive-intent delivery trace.
- Rate-limit the custom-symptom mutations (POST create, PATCH, DELETE) under a
  shared cycle:symptom:custom:<userId> bucket (30/min), mirroring the insights
  route, so a session can't flood the encrypted-label catalogue.
- Re-run the active-count cap inside PATCH when a symptom reactivates
  (hidden → active); a hidden row doesn't count toward the cap, so without the
  recheck a user could exceed it by hiding then re-enabling rows.
- Gate the log-day "limit reached" copy on the cap errorCode rather than any
  create failure, so a transient/validation error no longer masquerades as the
  cap being hit; thread the returned key so a new custom symptom auto-selects.
- Move the history-chart bar fill alphas into named phase-tokens
  (HISTORY_REST_OPACITY / HISTORY_PERIOD_OPACITY) so bar intensity lives in one
  token source alongside FLOW_OPACITY.
- Strengthen the confirmed-ovulation calendar marker (ring 1.5px → 2px, fill
  alpha 0.16 → 0.22) so it stays distinct from the predicted dot on small light
  screens; keeps its data-attr + aria naming.
- Raise the phase-education chip border from /10 to /15 for legible contrast on
  the tinted tile.
2026-06-06 17:19:59 +02:00
Marc-André Bombeck 985e197db4 feat(cycle): user-defined custom symptoms
Mirror the v1.13 custom-mood-tag CRUD for cycle symptoms. A user can mint
their own symptoms beyond the 15 seeded ones, picked from a label + icon
popover via a dashed ghost-chip in the log-day sheet's symptom grid.

- POST/GET /api/cycle/symptoms/custom and PATCH/DELETE on /[key]: mint a
  custom:<uuid> key, encrypt the free-text label at rest (AES-256-GCM),
  per-user cap, owner-scoped, gated behind requireCycleEnabled. Soft-hide by
  default, hard-purge on ?purge=true.
- The day-log write already resolves a symptom key against the catalogue plus
  the caller's own rows, so a custom: key links by construction.
- The log-day sheet fetches the user's customs and merges them under a new
  custom category in the chip grid; the ghost-chip + popover reuse the chip
  styling and the Lucide icon set.
- Seed the global custom symptom category (migration 0134).
- The purge route (DELETE /api/cycle/all) now drops the user's custom symptom
  rows too, so no intent-revealing label survives a purge.
- The label is never logged: only the icon + counts reach annotate/auditLog.
- i18n: cycle.symptom.custom.* across all six locales.
2026-06-06 16:54:43 +02:00
Marc-André BombeckandClaude Opus 4.8 d2da8d2657 fix(cycle): reset reproductive intent on the data purge
The hard-purge kept the CycleProfile row (so the gate + settings keep
working) but left the intent-revealing fields behind. Reset goal to
GENERAL_HEALTH and null the cycle/period/luteal priors inside the purge
transaction so no reproductive intent survives, matching the route's
"no reproductive trace persists" contract.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-06 13:41:49 +02:00
Marc-André BombeckandClaude Opus 4.8 3375bbabac fix(cycle): second-audit safety, privacy, and write-path hardening
Address the second-round audit (server cluster):

- AVOID_PREGNANCY surfaces the fertile window, so it must carry the
  stronger "not a contraceptive method" disclaimer — wire the existing
  strong copy via a shared cycleDisclaimerKey(goal), server and client.
- Re-anchor the FOLLOWING neighbour cycle on a back-filled period start so
  an inserted-between-two-cycles start no longer leaves a stale open cycle.
- Resolve the sensitive-category encryption flag once per bulk/import
  batch instead of re-reading the profile per day; hoist the bulk cycle
  attribution in-memory (no per-entry query).
- Drop the raw driver message from the bulk wide-event meta (annotate meta
  is not run through the central redactor); keep the stable reason + code.
- Hard-purge now clears the cycle audit trail and the cycle reminder rows
  in the push-attempts ledger, so no dated reproductive trace survives.
- Discreet mode collapses the Web-Push coalescing tag and drops cycle
  metadata (phase) from APNs, matching the existing APNs event collapse.
- Add the reproductive intent field names to the central redactor denylist.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-06 13:15:22 +02:00
Marc-André BombeckandClaude Opus 4.8 ac13dd28ed fix(cycle): honour the sensitive-data encryption toggle + harden the write path
- When the sensitive-category encryption preference is on (the default),
  encrypt the intent-revealing day-log fields (sexual activity, protection,
  pregnancy/progesterone tests, contraception) into an encrypted envelope and
  leave the queryable columns null, decrypting on read. The toggle now does
  what it says rather than storing those fields in plaintext.
- Make the day-log write a partial merge so a re-log only overwrites the
  fields it carries, never nulling untouched ones (no more data loss when the
  same day is logged again).
- Make the upsert collision-safe: a manual entry and an imported entry on the
  same date adopt one canonical row instead of throwing, and the single POST
  returns a clean conflict rather than a 500; one bad date no longer aborts a
  whole import.
- Compare the decrypted note, not the ciphertext, so an unchanged re-log is a
  true no-op and stops needless re-sync.
- Hoist cycle attribution out of the per-entry loop; map bulk failures to
  stable reason codes; audit the import goal change; mask the event name in
  discreet reminder pushes; add a one-tap hard-delete purge for all cycle data.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-06 11:49:23 +02:00