A private Android period tracker that learns your cycle. Kotlin, Jetpack Compose, offline-first.
Go to file
null d7fe58883c fix: restore docs/TOOLS.md, deleted by a stray test commit
Commit 6fc12b6 was a throwaway used to prove the pre-commit hook would accept a
deletion-only commit. It was meant to be discarded locally, and it was — but the
post-commit hook had already pushed it, so the deletion reached origin while the
local reset removed the commit from my branch.

Restored forward rather than force-pushed. The commit is on the remote, and
rewriting published history to hide a two-minute mistake is a worse trade than a
fix that explains itself.

The lesson is why this message is longer than the diff. `git reset --hard` after
a commit the post-commit hook has already pushed does not undo the push — and it
silently discarded the uncommitted hook fix I was in the middle of proving,
which is why the second proof then failed for a reason that had nothing to do
with the guard. Commit the work before proving the guard, not after.
2026-08-18 15:00:17 -05:00
.githooks fix: the pre-commit hook refused every deletion-only commit 2026-08-18 15:00:03 -05:00
app feat: fertility that declines rather than stretches 2026-08-18 14:56:05 -05:00
core feat: fertility that declines rather than stretches 2026-08-18 14:56:05 -05:00
docs fix: restore docs/TOOLS.md, deleted by a stray test commit 2026-08-18 15:00:17 -05:00
domain chore: ignore .kotlin/, the compiler's session directory 2026-08-18 14:57:07 -05:00
gradle feat: period CRUD end to end, and stop a double tap killing the app 2026-08-18 02:52:35 -05:00
scripts feat: Room database for cycle history, and the schema guard that actually works 2026-08-18 02:32:07 -05:00
.gitignore chore: ignore .kotlin/, the compiler's session directory 2026-08-18 14:57:07 -05:00
README.md chore: adopt the project template and add the Kotlin/Compose skeleton 2026-08-18 02:16:47 -05:00
build.gradle.kts feat: module boundary guard, and two API-level bugs it uncovered 2026-08-18 03:00:11 -05:00
gradle.properties chore: adopt the project template and add the Kotlin/Compose skeleton 2026-08-18 02:16:47 -05:00
gradlew chore: adopt the project template and add the Kotlin/Compose skeleton 2026-08-18 02:16:47 -05:00
gradlew.bat chore: adopt the project template and add the Kotlin/Compose skeleton 2026-08-18 02:16:47 -05:00
settings.gradle.kts feat: repository layer, and stop backfilled history fabricating accuracy figures 2026-08-18 02:41:05 -05:00

README.md

Period

Status: Draft
Owner: _null
Last reviewed: 2026-08-18
Governs: README.md as the project-facing overview for Period
Review trigger: The first buildable feature release; any change to the stack, the
                privacy promise, or how the project is built and run

A private Android period tracker that learns your cycle rather than the average person's — and never sells your data.

What it is | Status | Build and run | Repository map | Project docs | Agent notes

{ Android / Google Play | Kotlin + Jetpack Compose | Room, offline-first | Personalized prediction with an honest window | Discreet notifications | No account, no server, no data sale }

The core loop is:

Track → Learn → Predict → Remind → Learn again

What it is

A focused tracker that answers one question well: when is my next period likely to start? It records confirmed period dates, learns the individual's pattern from them, and produces a most-likely date with a window and a confidence label — never a bare date presented as fact. From that it estimates ovulation and the fertile window, and it asks discreetly whether the period started, using both yes and not yet to improve the next forecast.

Two promises hold the product up:

Your period. Better predicted. — once there is enough personal history, the app must not fall back to a generic 28-day cycle. A user recording 34, 35, 36, 34, 35 who is predicted 28 is a core product defect, not a tuning issue.

Your cycle belongs to you. We will never sell period history, fertility information, ovulation estimates, cycle predictions or personally identifiable information — to advertisers, data brokers or anyone else.

The second is enforced structurally, not remembered: health data cannot reach the advertising subsystem, because the module that will hold ad code declares no dependency on the cycle database or the prediction domain, and a guard proves it.

What it deliberately is not — no community, no pregnancy mode, no chatbot, no article feed, no symptom encyclopedia, and not contraception — is in docs/planning/PROJECT_PLAN.md.

Status

Skeleton. There is no usable app yet, and this section says so rather than listing features that do not exist.

Surface Status Evidence
Documentation tree and tracker convention Adopted this tree; milestones and issues in the tracker
Gradle / Kotlin / Compose project Builds ./gradlew assembleDebug and assembleRelease both pass
Material 3 theme and tokens Built core/designsystem
Four-tab navigation shell Built, placeholder screens app/src/main/kotlin/dev/privacyllc/period/navigation/PeriodApp.kt
Cycle model and interval derivation Built domain/cycle, 5 tests
Prediction engine Baseline only domain/prediction, 12 acceptance tests from PRODUCT_PLAN §51
Room persistence, DataStore Not built Batch 01
Onboarding, Today, Calendar, Insights Not built Batch 03
Fertility, notifications, privacy features, monetization Not built Batches 0407
QA No round run docs/qa/ClaudeReport.md

BaselinePredictionEngine is a robust-median prototype and is not the product. PRODUCT_PLAN §11 names a plain average as an acceptable prototype and an unacceptable final engine; Batch 02 replaces it with the recency-weighted, trend-aware, "not yet"-conditioned engine §12 specifies. The acceptance tests exist now so that replacement can be shown to be better rather than merely different.

Build and run

Prerequisites: a JDK 21 and the Android SDK with platform 37 and build-tools 37.0.0 installed, ANDROID_HOME set.

git config core.hooksPath .githooks   # per clone, every clone — see below

./gradlew test                        # JVM suites, no emulator, ~1s
./gradlew assembleDebug               # app/build/outputs/apk/debug/
./gradlew assembleRelease             # minified, unsigned

compileSdk is 37 because the current AndroidX libraries require it; targetSdk is 36, Google Play's floor for new apps from 2026-08-31. They are deliberately different — raising targetSdk opts the app into runtime behaviour changes and is a tested decision, not a build fix.

git config core.hooksPath .githooks is per clone. Without it the hooks are not installed and fail silently, which is the failure mode they exist to prevent. post-commit pushes; see docs/architecture/githooks/README.md.

Repository map

app/                     application module, MainActivity, navigation shell
core/designsystem/       Material 3 theme and colour tokens
domain/cycle/            pure Kotlin — period records, cycle derivation
domain/prediction/       pure Kotlin — the forecast, window and confidence
scripts/                 the six template scripts this project adopted
.githooks/               pre-commit, commit-msg, post-commit
docs/                    product, architecture, design, QA, security, history

domain/* are kotlin("jvm") modules and cannot see the Android SDK. That is on purpose: the prediction engine is the product, so it needs the most tests, and tests that need an emulator are tests that do not get run.

Project docs

Detailed procedures belong in docs; open work belongs in the tracker, not in this README.

Doc Purpose
docs/DOC_TRUST_MAP.md Which document owns which answer, and which source wins when records disagree. Read this first.
docs/planning/PRODUCT_PLAN.md The full V1 specification — prediction requirements, screens, copy, compliance
docs/planning/PROJECT_PLAN.md The short vision, the stack and its reasons, and what this is deliberately not
docs/WORK_CYCLE.md What happens at the end of a piece of work
docs/architecture/README.md Modules, boundaries, data shapes, migrations
docs/design/README.md Tone, and the four rules that settle design arguments
docs/security/SECURITY.md Threat model, the advertising boundary, logging rules
docs/TOOLS.md Which script to run, and which ones can stop you

Where the tracker is

Milestones are batches, issues are deliverables, and severity labels are exactly P0, P1, P2 and release-blocker. Credentials come from the environment, never from this repository:

set -a; . ~/.openclaw/docker-registry.env; set +a
python3 scripts/forgejo-issue.py list

Two traps that cost an hour each otherwise: Cloudflare 1010-blocks clients that do not look like a browser or curl, so every request needs User-Agent: curl/8.5.0forgejo-issue.py sends it and anything new must too. And /issues returns pull requests unless type=issues is passed.

Agent notes

  • Product truth comes from the code and the tracker before prose.
  • Do not keep a work list in this README or anywhere else in docs/.
  • Finish with docs/WORK_CYCLE.md, every time: close what you finished with the evidence, close the milestone if the batch landed, update the documents the change triggered in the same commit, push, log the entry, then reconcile and write the summary and next action.
  • Nothing on privacyllc.dev writes itself except the tracker counts and the pushed docs.
  • Do not claim a feature is built unless you can cite the file, test or screenshot that proves it. The status table above is the standard.
  • Verify current stable versions before changing the toolchain. The ones in gradle/libs.versions.toml were checked against the official sources on 2026-08-18; PRODUCT_PLAN.md asks for that check rather than for its own numbers to be trusted.
  • Never put a cycle date, a prediction or a fertility state into a log, an analytics event, or an ad request.