Ovumcy
Privacy-first, self-hosted menstrual cycle tracker
Alternative to: flo, clue

v1.9.0
2026-07-19Reworks BBT ovulation detection into a single shared “3-over-6” coverline detector with disturbance rejection, and lands a batch of external-audit remediations across cycle logic, onboarding privacy copy, import performance, and accessibility. No database migrations; no breaking changes.
Changed
- BBT ovulation detection unified on a “3-over-6” coverline with disturbance
rejection. One detector (
detectBBTShiftFirstHighDay) now drives all three surfaces — luteal-phase inference, the calendar tentative-ovulation signal, and the stats BBT chart coverline + marker — which previously disagreed. The coverline is the max of the six immediately preceding undisturbed recorded temperatures; a shift is three calendar-consecutive days strictly above it with the third day ≥ coverline + 0.2 °C; ovulation is dated to the day before the first elevated day. Readings on days taggedillness/sleep_disruptionare excluded from the detection series so a fever can neither inflate the coverline nor confirm a streak (display series unchanged). The chart draws the coverline only once a shift is confirmed; the accessibility summary gains a no-shift sentence; all six locales adopt coverline terminology. (#255, #250) - Age group is genuinely optional. A “not specified” option replaces the
silent
under_40onboarding default. (#250) - Import writes days in one batch — a single range read plus one chunked
CreateBatchinstead of lookup-and-insert per day (with a 20k-unique-date load test), speeding up large restores. (#250)
Fixed
- Settings section navigation stays pinned below the header while scrolling on
desktop (
>=640px); on mobile it remains a static top-of-page index. (#252) - A period day with unset flow now reads “not specified” instead of “none”, which had wrongly implied “no bleeding”. (#250)
- The calendar distinguishes projected (future / auto-filled) period days from logged facts, which were previously indistinguishable. (#250)
- Removed the false “we do not store identity data” onboarding claim in all six locales — email and display name are stored. (#250)
- Accessibility: password-reveal and mobile-menu tap targets raised to 44px; the prediction disclaimer given a visible accent; the onboarding quick-pick group labelled. (#250)
Security
- Cloud/IMDS webhook guidance.
docs/notifications.mdnow documents that on a cloud host an owner-configured webhook URL can reach the instance metadata endpoint (169.254.169.254) when the private-address gate is off, and recommendsWEBHOOK_BLOCK_PRIVATE_ADDRESSES=truefor any cloud (non-LAN) deployment. The delivery envelope already discards the response body (no exfiltration path); the residual risk is blind internal-network probing by a co-tenant owner. Runtime behavior and defaults are unchanged. - Startup warning for the exposed webhook combination — the server logs a boot
warning when
REGISTRATION_MODE=openis combined withWEBHOOK_BLOCK_PRIVATE_ADDRESSES=false, surfacing the multi-owner / publicly-reachable SSRF exposure. Defaults unchanged. (#250)
Internal
- Oversized files split to cut merge/regression risk (#251, #253); the CLI command
dispatcher and the
webhook setargument parser decomposed below the gocyclo-15 gate (#256). - Test hygiene: onboarding i18n date-field tests collapsed into one table-driven test with Italian coverage (#257); round-3 mutation sweeps split from six theme aggregates into per-source files, dropping one duplicate (#258); v1.8.x mutation-hardening baseline (#246).
- A dependency-free route↔OpenAPI contract test and a
checkStyledControle2e helper centralizing 12force:trueclicks (#250); README contents / Quick-Start / digest-pin guidance and.bin/excluded from the Docker build context (#250); codecov PR-comment noise suppressed (#254); GitHub Actions and npm dev-dependency bumps (#247, #248).