The project exists and persists data. Kotlin/Compose/Material 3 skeleton, Room and DataStore behind a repository layer, the four-tab navigation shell, and period record CRUD.
Landed when: a user can add, edit and delete a confirmed period on a device, the records survive a force-stop and a Room migration, and ./gradlew test passes with the module-boundary guard proved to fail.
The product's core intellectual property. Replaces BaselinePredictionEngine with the engine PRODUCT_PLAN.md §12 specifies: recency weighting, a robust centre, variability-driven windows, trend detection, prediction snapshots and recalculation on every confirmed start, edit and 'Not yet'.
Landed when: all §51 acceptance cases pass against the new engine, PredictionRecord snapshots are stored before outcomes are known, and the engine is measurably better than the baseline rather than merely different.
Batch 03 — Core UX
The screens a user actually meets. Onboarding, the Today screen with its six dynamic states, period start and end logging, the calendar, and insights.
Landed when: a first-time user can install the app, enter their last period, and reach a forecast they believe — without help and without an account.
Batch 04 — Fertility
Estimated ovulation and the fertile window, presented as estimates and never as certainty.
Landed when: ovulation and fertile window appear on Today and on the calendar with distinct non-colour markers, the not-contraception disclaimer appears everywhere fertility does, and no copy anywhere says 'safe' or 'unsafe'.
Discreet reminders that are useful without exposing menstrual information. Period approaching, expected-today check-in, 'Did it start?', period-end check-in, fertility toggles — all through WorkManager, with no exact-alarm permission.
Landed when: QA pass F confirms on a real lock screen that Discreet and Maximum privacy modes leak nothing, at every notification type, and repeated 'Not yet' stops nagging rather than repeating forever.
The promises made visible and enforceable. Biometric and PIN lock, export, irreversible delete, the privacy promise in onboarding and settings, and the secure logging rules made real in release builds.
Landed when: the release build has no cycle date reachable in any log or crash payload, Delete My Data actually deletes, export produces only the user's own data, and the security checklist is worked through end to end.
Batch 08 — Polish
Dark mode, accessibility, restrained motion, the incognito launcher option, performance, final artwork and store assets.
Landed when: TalkBack completes the core loop, the calendar is readable in greyscale at the largest font scale, the Today screen renders without a visible ad-load layout shift, and the store listing and Data Safety declaration are ready to submit.
The prediction engine measured rather than read: a learning-curve evaluation (40 seeded simulations per cycle-profile, mirroring the app's own error-feedback loop) established that the engine reaches its accuracy floor within 2-3 cycles for stable users and is honestly uncertain for genuinely variable ones — and surfaced defects in the learning loop that silently cost or falsified error samples, three measurable calibration gaps, and a set of scientific constants carrying no citation.
This batch fixes the loop, calibrates the engine against its own measurements, commits the simulation as a permanent guard, and writes down the evidence for every constant that encodes a claim about menstrual physiology.
Landed when: the learning-curve guard is committed and green at its final thresholds, every defect below is closed with a named regression test, and docs/architecture/SCIENCE.md exists with a verdict for each biology constant.
Three ideas surfaced by reviewing sandipkalola/menstrual_cycle_widget against this app. All three surface or move data the app already holds — no new collection, no change to the privacy surface, nothing that makes this a women's health super-app (PRODUCT_PLAN §5).
Their prediction math offered nothing: last start plus a configured 28-day default, no window, no confidence, no learning — the exact naive baseline §11 forbids. What it did show is which honest versions of its features are worth having.
Landed when: each issue below is closed with its evidence, or explicitly deferred with a reason.
A user reported that Settings → App lock could not be left. Tracing it found three causes (the bottom-nav back-stack restore pins the Settings tab to its child; no settings page has a back arrow; setting a PIN can race the gate and lock you out of the session you set it in), a real security bug beside them (Change PIN never checks the current PIN), and a lock model that offers biometrics only as a shortcut on a mandatory PIN.
This batch gives every settings page a way back, makes the Settings tab always land on its root, fixes the bypass and the race, lets the user choose a PIN just for this app, fingerprint or face, or either — with copy that says plainly the PIN is the app's and not the phone's — and brings the lock's error handling, tests and security docs up to what ships.
Landed when: every issue below is closed with its named test or device-row evidence; a settings sub-page cannot be entered without a back arrow; SECURITY.md describes the lock that ships and no phantom biometric key.
A read-only trace of the reminder pipeline found that the period-end check-in's buttons write the wrong records (tapping 'Ended' inserts a new period starting today), that actions carry no date and so apply to whatever day they are tapped on, that the 'stop asking after three' counter resets on every process start, that a denied notification permission still counts as having asked, and — verified against WorkManager's bytecode — that changing the reminder time after the first reminder has fired does not move it.
This batch fixes each, gives the worker an error taxonomy instead of silent success, dismisses a reminder once it is answered, and puts ReminderWorker, NotificationActionHandler and the scheduler under test for the first time.
Landed when: every issue below is closed; no reminder action can write the wrong record or the wrong day; ReminderWorker, NotificationActionHandler and the scheduler each have a named test; the screen copy about timing is true.