206 lines
12 KiB
Markdown
206 lines
12 KiB
Markdown
# Development log — Period
|
||
|
||
```
|
||
Status: Current
|
||
Owner: _null
|
||
Last reviewed: 2026-08-18
|
||
Governs: the dated record of what happened
|
||
Review trigger: Nothing. This file is appended to, never revised.
|
||
```
|
||
|
||
## How to use this
|
||
|
||
Newest first. **One entry per work session**, written before you stop — that is
|
||
step 6 of `docs/WORK_CYCLE.md`, and the two lines it insists on are `Next
|
||
action` and `Blockers`.
|
||
|
||
Those two are not decoration. The next session starts by reading the top of this
|
||
file, and a session that ended without saying what came next hands the one after
|
||
it a re-derivation instead of a starting point — which is where drift enters.
|
||
Neither line competes with anything: the live next action is the field on the
|
||
project at privacyllc.dev and the live blockers are issues in the tracker, while
|
||
these say what both were **at this date**. A record of then never disagrees with
|
||
a record of now.
|
||
|
||
**Append-only by convention.** Correcting an old entry rewrites the record of
|
||
what was known at the time, which is the one thing this file is for. If an entry
|
||
turns out to be wrong, add a later entry saying so; do not edit the first.
|
||
|
||
Note the Review trigger above says "nothing", deliberately. A dated log cannot
|
||
rot the way a description of current state can — the entries were true when
|
||
written and stay true. It is exempt from review for the same reason a receipt is.
|
||
|
||
## Entries
|
||
|
||
### 2026-08-18 — Batch 02 shipped: the prediction engine, and three bugs the tests found
|
||
|
||
All five Batch 02 issues closed and the milestone closed with them. The app now
|
||
ships the engine PRODUCT_PLAN.md §12 specifies rather than the prototype it was
|
||
explicitly labelled as.
|
||
|
||
**The shape is the design.** `PersonalPredictionEngine` keeps a discrete
|
||
probability distribution over candidate start dates, not a date with a margin.
|
||
The mode is the forecast, the window is the narrowest span holding 80% of the
|
||
mass, and a "Not yet" is the distribution conditioned on what the user said.
|
||
§13's requirement — that a "Not yet" updates the date, the window *and* the
|
||
confidence rather than shifting a fixed prediction by a day — is not extra work
|
||
in that design; it is the only thing that structure can do.
|
||
|
||
**It is better, as a number.** `EngineComparisonTest` scores both engines over
|
||
the §51 fixtures on every build: mean absolute error 0.67 against 1.00, and the
|
||
window contained the actual start 9 times out of 9 against 7.
|
||
|
||
**Coverage is the measure, not width — and measuring taught that.** The first
|
||
version of the comparison asserted the new windows must not be wider. It failed,
|
||
and it was the assertion that was wrong: where the personal engine is wider, it
|
||
is right to be, and the baseline answers a history with a suspected missing
|
||
period using a two-day window and misses. A window that misses is a broken
|
||
promise rather than a tight forecast.
|
||
|
||
**Three modelling bugs, each found by a test failing rather than by reading:**
|
||
|
||
1. Median absolute deviation alone reads a user alternating 25 and 37 days as
|
||
perfectly consistent — half her deviations are zero. Twenty disagreeing
|
||
cycles came back High, which is exactly the §15 rule about volume not buying
|
||
confidence. Spread is now the larger of MAD and mean absolute deviation.
|
||
2. Recency weighting assumes the recent past predicts the near future. For a
|
||
variable user that is false, and weighting it equally cost three days on the
|
||
§51 variable fixture. Recency is now trusted in proportion to how much her
|
||
cycles agree — which is a better statement of what recency weighting is
|
||
actually for.
|
||
3. A fixed one-day floor on trend detection fired on a 42-day-cycle history
|
||
whose medians differed by one day. One day is a real trend at 28 and rounding
|
||
error at 42, so the floor is relative to the user's own spread.
|
||
|
||
That is now three consecutive pieces of work — the schema guard, the boundary
|
||
guard, and the engine — where the thing that found the defect was **running it,
|
||
not reading it**. Worth stating as a habit rather than a coincidence.
|
||
|
||
**Wired through, not just tested.** `PredictionInput` carries recent absolute
|
||
errors and the repository feeds scored errors back in. Without that the app
|
||
would store every error it makes and never read one back — measuring accuracy
|
||
rather than learning from it, with §12 step 5's widening happening only in a
|
||
unit test.
|
||
|
||
- **Closed:** #10, #11, #12, #13, #14 — and the `Batch 02 — Prediction Engine`
|
||
milestone, which closing the last issue does not do.
|
||
- **Next action:** Batch 03 — Core UX. The onboarding flow in §19, the designed
|
||
Today screen and its six dynamic states in §21–§22, period start and end
|
||
logging in §23–§24, the calendar in §26 and Insights in §27. The working
|
||
surface built in Batch 01 is deliberately ugly and says so on screen; it is
|
||
the thing Batch 03 replaces. Before starting, file the Batch 03 issues — the
|
||
milestone exists and is empty.
|
||
- **Blockers:** Unchanged and both need a person. #8, the three branding marks,
|
||
which an agent must not fake. #9, the webhook, whose URL and secret are not
|
||
readable from this machine — until it is registered an opened `P0` raises no
|
||
alert at all. Neither blocks Batch 03.
|
||
|
||
### 2026-08-18 — Batch 01 foundation: seven of nine issues, and three guards that were wrong
|
||
|
||
Room, DataStore, the repository layer, period CRUD end to end on a device, and
|
||
the module boundary guard. #3 to #7 closed; #8 (branding) and #9 (webhook) are
|
||
the two that need a person rather than an agent.
|
||
|
||
**What was built.** `core/database` with the four entities from §10, DAOs
|
||
returning `Flow`, and the schema exported and committed. `core/datastore` for
|
||
settings, deliberately separate from the cycle database so Delete My Data cannot
|
||
reset a privacy choice the user made. `core/data` as the seam: domain types out,
|
||
cycles derived rather than stored, the forecast a function of the data instead
|
||
of a field somebody has to refresh. Hilt wiring and a working Today surface that
|
||
says "Batch 01 · working surface" so nobody mistakes it for the designed screen.
|
||
|
||
**Three guards were written, and all three were wrong at first.** This is the
|
||
day's real lesson and it is worth carrying forward:
|
||
|
||
1. `SchemaTest` looked like a Room schema-drift guard. Room regenerates the
|
||
schema export during compilation, so both sides of every comparison agreed by
|
||
construction — adding a column without bumping the version left it green.
|
||
`scripts/schema-guard.sh` asks git instead, which Room cannot overwrite.
|
||
2. `checkModuleBoundaries` reported "7 modules checked, no violations" while
|
||
checking nothing: the root project is configured before its subprojects, so
|
||
every configuration read as empty. Caught by `prove-guard.sh` on its first
|
||
run. Collection moved to `afterEvaluate`, and the task now throws rather than
|
||
passing when it examined nothing.
|
||
3. The repository let `SQLiteConstraintException` escape into
|
||
`viewModelScope.launch`, so **tapping the primary button twice killed the
|
||
app** — found by hand on an emulator, with 70 unit tests green.
|
||
|
||
Each was caught by actually trying to break it. None would have been caught by
|
||
reading the code, and two of them would have been trusted for months.
|
||
|
||
**Two more bugs came from wiring the guard into `./gradlew check`**, which ran
|
||
Android lint for the first time: `LocalDate.ofInstant` and `LocalDate.EPOCH` are
|
||
API 34 and `minSdk` is 26. Both sit on the recalculation path — a crash on every
|
||
device below Android 14, invisible to the unit tests and to an API 36 emulator.
|
||
|
||
**QA.** Round 1 recorded as partial in `docs/qa/`. Passes A and B green, C and D
|
||
and H partial, the rest not run and each saying why.
|
||
|
||
- **Closed:** #3, #4, #5, #6, #7
|
||
- **Next action:** Batch 02 — replace `BaselinePredictionEngine` with the engine
|
||
§12 specifies: recency weighting, a robust centre, variability-driven windows,
|
||
trend detection, and "not yet" as a real conditioning step rather than a floor
|
||
on the window. The §51 acceptance tests already exist and must keep passing
|
||
against the new engine, which is what makes the replacement demonstrably
|
||
better rather than merely different. Before that, one cheap thing worth doing:
|
||
run the app once on a device at `minSdk` 26, because nothing here ever has.
|
||
- **Blockers:** None for code. #8 needs the three branding marks drawn — an
|
||
agent must not fake them, so the project card shows an initials tile until
|
||
somebody does. #9 needs the Command Center's webhook URL and secret, which are
|
||
not readable from this machine; until it is registered an opened `P0` raises
|
||
no alert at all.
|
||
|
||
### 2026-08-18 — Template adopted; Kotlin/Compose skeleton builds
|
||
|
||
Period went from a bare directory holding one specification file to a git
|
||
repository with the standard documentation tree, a tracker, and a project that
|
||
compiles. Adoption followed `Projects/Template/START-HERE-New-Project.md`.
|
||
|
||
**Documents.** `scaffold.sh` created 19 paths, 0 skipped. The specification moved
|
||
from `Docs/period_tracker_product_plan.md` to `docs/planning/PRODUCT_PLAN.md`
|
||
unchanged in substance, with a status header added; the capitalised `Docs/` is
|
||
gone, since every script and the Command Center expect the lowercase tree. Every
|
||
scaffolded document was filled in for Period rather than left with placeholders.
|
||
docs/OPERATIONS.md was deleted — an offline app is not a deployed service.
|
||
`docs/DOC_TRUST_MAP.md` was written last and describes what is actually here,
|
||
including a section naming what this project deliberately does **not** have.
|
||
|
||
**Code.** Four Gradle modules: `app`, `core/designsystem`, and `domain/cycle`
|
||
and `domain/prediction` as `kotlin("jvm")` so the engine is testable without an
|
||
emulator. 17 tests pass, 12 of them the acceptance cases from `PRODUCT_PLAN.md`
|
||
§51. `BaselinePredictionEngine` is a robust-median prototype and is explicitly
|
||
not the product — it exists so Batch 02's replacement can be shown to be better
|
||
rather than merely different.
|
||
|
||
**Three things that cost time and are worth knowing next session:**
|
||
|
||
- **AGP 9 ships Kotlin built in.** Applying `org.jetbrains.kotlin.android` is now
|
||
a hard error, not a redundancy. The Compose compiler plugin is still separate.
|
||
- **Current AndroidX requires `compileSdk 37`.** Only up to 36 was installed;
|
||
`platforms;android-37.0` and `build-tools;37.0.0` were installed into
|
||
`~/Android/Sdk`. `targetSdk` stays at 36 — Play's floor from 2026-08-31 — and
|
||
the two being different is deliberate, not an oversight to tidy up.
|
||
- **Versions were verified, not inherited.** Kotlin 2.4.10, AGP 9.3.1, Gradle
|
||
9.7.0, Compose BOM 2026.08.00, Room 2.8.4, Hilt 2.60.1 — each checked against
|
||
its official source today, which `PRODUCT_PLAN.md` asks for rather than
|
||
trusting its own numbers.
|
||
|
||
**Tracker.** Eight milestones opened, `Batch 01 — Foundation` through
|
||
`Batch 08 — Polish`, and nine issues filed under Batch 01 only. Seven milestones
|
||
are deliberately empty: the roadmap is genuinely known and worth being visible,
|
||
but the work items under it are not, and inventing them would make every tracker
|
||
percentage permanently wrong. `forgejo-issue.py check` warns about this, and the
|
||
warning is correct about the mechanism and expected here.
|
||
|
||
- **Closed:** #1, #2
|
||
- **Next action:** Start issue #3 — core/database with Room entities for
|
||
`PeriodRecord`, `SpottingRecord`, `PredictionRecord` and `NotYetObservation`,
|
||
DAOs returning `Flow`, schema export committed, and a version-1 migration test
|
||
that proves the harness works before there is a migration that matters. Its row
|
||
goes in `docs/architecture/README.md`'s migration table in the same commit.
|
||
- **Blockers:** None for the code. Two things need a person rather than an agent:
|
||
the three branding marks (#8), which cannot be drawn here and must not be
|
||
faked, and the Command Center webhook (#9), whose URL and secret are not in any
|
||
credential file readable from this machine. Without the webhook an opened `P0`
|
||
raises no alert at all — it waits for the next reconcile.
|