454 lines
26 KiB
Markdown
454 lines
26 KiB
Markdown
# Development log — Privacy: Period Tracker
|
||
|
||
```
|
||
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 — Renamed to Privacy: Period Tracker
|
||
|
||
"Period" was always a working name — `PRODUCT_PLAN.md` §55 said so — and the real
|
||
identity arrived with the brand guide. Everything that called the project Period
|
||
now calls it **Privacy: Period Tracker**.
|
||
|
||
**The rename was mostly about deciding what NOT to touch.** "Period" is the
|
||
product name and the central domain word at the same time. `PeriodRecord`,
|
||
`PeriodWriteResult`, `confirmPeriodStart`, the `period_records` table and the
|
||
button reading "Started period" all describe a menstrual period rather than a
|
||
product — 239 references, every one of them left alone. A find-and-replace would
|
||
have turned the data model into nonsense. Sixteen files changed in total.
|
||
|
||
Also deliberately unmoved, and written down so the absence reads as a decision:
|
||
|
||
- **The package `dev.privacyllc.period` and `applicationId`.** They already read
|
||
correctly under the new name — `privacyllc` is the company, `period` the app —
|
||
and changing them would rewrite 68 files, rename the Room schema directory and
|
||
break the hardcoded path in `schema-guard.sh`.
|
||
- **`period.db`.** Renaming a database file orphans the data on every device
|
||
that already has it. The emulator's existing install opened fine afterwards,
|
||
which is the proof.
|
||
|
||
**The repository was renamed in place**, not recreated: `null/Period` →
|
||
`null/Privacy-Period-Tracker`, keeping all 30 commits, 27 issues, 8 milestones
|
||
and 4 labels. Counted on both sides rather than assumed, because "a rename is
|
||
lossless" is exactly the sort of thing that is true until it is not.
|
||
|
||
**Three names have to agree**, and only two of them are obvious: the Forgejo
|
||
repository, the git remote, and the Command Center's `forgejoRepo` mapping. The
|
||
third is the silent one — a stale mapping degrades to *"pointing at a repository
|
||
that does not exist"* on the next reconcile and the site quietly stops counting.
|
||
All three verified, and the reconcile afterwards reported `0 pointing at a
|
||
repository that does not exist`.
|
||
|
||
**The Command Center slug could not be renamed.** `PATCH` accepted a `slug` field
|
||
and ignored it; `period` still resolves and `privacy-period-tracker` returns 422.
|
||
It is an internal key, invisible on the card, and recreating the project to
|
||
change it would throw away its history — so it stays, recorded here rather than
|
||
retried later by somebody who assumes it was missed.
|
||
|
||
**One inconsistency created on purpose.** The canonical name has a space after
|
||
the colon; the supplied wordmark does not. `BRAND_GUIDE.md` §10 now says the
|
||
space form and says plainly that the drawn mark disagrees. Filed as #28 rather
|
||
than papered over, because redrawing a lockup needs an artist.
|
||
|
||
- **Closed:** nothing — this is a rename, not a deliverable. #28 filed.
|
||
- **Next action:** Batch 06 — Privacy and Security, unchanged by the rename. File
|
||
its issues first; the milestone is empty. Biometric and PIN lock, export,
|
||
irreversible delete, the privacy promise in Settings, and making §45's logging
|
||
rules true in a release build.
|
||
- **Blockers:** #9, the Command Center webhook, still needs a person. Its body
|
||
named the old repository path and has been corrected in the tracker, so
|
||
whoever picks it up gets a command that works. The four QA gaps are unchanged
|
||
and still the better spend: nobody has looked at a real lock screen, TalkBack has never been run,
|
||
text has never been scaled, and nothing has run at `minSdk` 26.
|
||
|
||
### 2026-08-18 — Batch 05 shipped, and the app got its real brand
|
||
|
||
Reminders landed and the milestone closed. **Five of eight batches are done, and
|
||
exactly one issue is open in the whole tracker** — #9, the webhook, which needs a
|
||
person.
|
||
|
||
**Notifications, and the two Android behaviours that leak if you trust the
|
||
docs.** `NotificationCopy` is a pure function, so every kind × every mode is
|
||
tested exhaustively without an emulator — including the action labels, which §31
|
||
points out are visible text too. A perfectly discreet body under a button
|
||
reading "Started my period" leaks anyway.
|
||
|
||
Then the device found what the unit tests could not:
|
||
|
||
1. **A private notification with no public version does not blank the lock
|
||
screen — it shows the private text.** `NotificationText` therefore has no
|
||
nullable title, and an instrumented test asserts every kind attaches one.
|
||
2. **A notification channel is immutable after creation.** Importance and
|
||
lock-screen visibility cannot be changed, so one shared channel would have
|
||
kept whatever the user's first privacy mode set, forever. Switching from
|
||
Direct to Maximum privacy would have *appeared* to work and changed nothing.
|
||
One channel per mode now.
|
||
|
||
**A third guard failed its first proof.** `checkPermissions` — new, and worth
|
||
having, because adding WorkManager silently brought four permissions that appear
|
||
in the Play listing — read a stale merged manifest because it did not depend on
|
||
the task that writes one. An injected `SCHEDULE_EXACT_ALARM` went unnoticed.
|
||
That is the schema guard, the boundary guard and now the permission guard: three
|
||
for three. **Assume a new guard is broken until it has been watched failing.**
|
||
|
||
**And a mistake of mine worth keeping.** While proving the pre-commit fix I
|
||
committed a throwaway and ran `git reset --hard HEAD~1`, which did not undo the
|
||
push the post-commit hook had already made and silently discarded the
|
||
uncommitted fix I was mid-proof on. Reconciled forward rather than rewriting
|
||
published history. Commit the work before proving the guard.
|
||
|
||
**The brand arrived mid-session.** The owner supplied a full visual guide and
|
||
artwork, which answers #8 — the three marks an agent must not fake now exist
|
||
because a person made them. Converted to the required webp with two things
|
||
handled rather than blindly transcoded: the sources render their rounded corners
|
||
against black, and the lockup is square so a 3:1 crop would have cut the wordmark
|
||
in half.
|
||
|
||
The guide **supersedes §39's colour direction**, and one conflict is real: §39
|
||
wants a muted teal fertile window and the guide is plum-and-rose throughout with
|
||
a pink for fertility. The guide wins, and it is safe for it to win *because the
|
||
calendar's states differ in shape rather than colour* — which is what §43
|
||
actually requires. Written down in `docs/design/README.md` rather than left to be
|
||
found.
|
||
|
||
- **Closed:** #8, #24, #25, #26, #27 — and the `Batch 05 — Notifications`
|
||
milestone.
|
||
- **Next action:** Batch 06 — Privacy and Security. File its issues first; the
|
||
milestone is empty. The work is biometric and PIN lock, export, irreversible
|
||
delete, the privacy promise in Settings, and making §45's logging rules true in
|
||
a release build. Much of the groundwork exists — `deleteAllHealthData` is
|
||
written and tested, backup is already excluded in the manifest — so this batch
|
||
is largely surfacing what the data layer can already do, plus the designed
|
||
Settings screen the Batch 05 working surface is standing in for.
|
||
- **Blockers:** One issue, and it needs a person: #9, the Command Center webhook.
|
||
Until it is registered an opened `P0` raises no alert at all. The QA gaps are
|
||
now the more pressing thing: **nobody has looked at an actual lock screen**,
|
||
TalkBack has never been run, text has never been scaled, and nothing has run
|
||
at `minSdk` 26. All four are cheap and none has ever been done.
|
||
|
||
### 2026-08-18 — Batch 04 shipped: fertility that declines, and a hook that refused every deletion
|
||
|
||
Three issues closed and the milestone with them. Four of the eight batches are
|
||
now done.
|
||
|
||
**Fertility, and the decision that made it worth shipping.** Ovulation is
|
||
estimated a luteal phase before the *predicted* next period — §17 asks for that
|
||
direction because the luteal phase is the stable half of the cycle — and the
|
||
window opens five days before it and closes one after. The uncertainty is
|
||
inherited from the forecast rather than invented.
|
||
|
||
The first version was arithmetically correct and useless. A user one cycle into
|
||
the app was shown a fertile window of **8 Aug – 24 Aug**: seventeen days, over
|
||
half a cycle, dressed up as a feature. So the estimate now returns null past a
|
||
usable uncertainty and the screen says *"Not enough history to estimate"* with a
|
||
reason to keep logging. Three stable cycles later the same user gets 12 Aug – 20
|
||
Aug, which is worth reading. Both states were checked on a device.
|
||
|
||
That is the third instance of one pattern, so it is now written into
|
||
`docs/architecture/README.md` as a rule rather than a coincidence: **the app
|
||
declines rather than stretches.** Accuracy figures wait for three scored
|
||
forecasts; averages wait for two intervals; fertility waits for a forecast tight
|
||
enough to hang off.
|
||
|
||
**§18's prohibition is a type.** `FertilityLikelihood` has LOWER, HIGHER and
|
||
UNKNOWN and no fourth value, and a test asserts no label contains a permission
|
||
word — so adding "safe" is a deliberate act with a failing test rather than a
|
||
slip in a string.
|
||
|
||
**Another greyscale collision.** The ovulation star was centred and sat directly
|
||
behind the numeral; in greyscale the "18" and the mark merged. Ovulation is now
|
||
the fertile ring plus a small star low in the cell, which is also the more honest
|
||
picture — that day *is* inside the window.
|
||
|
||
**The hook refused every deletion-only commit.** `secrets.sh` exits 2 for
|
||
"nothing was scanned", which is the correct answer for a commit that only
|
||
deletes files. The hook treated any non-zero as a refusal and printed *"possible
|
||
credential in the staged changes"* over a plain `git rm`. Found while removing a
|
||
`bin/` directory Buildship had been writing — a directory full of `.kt` files,
|
||
which is the one kind of build output that does not look like build output in a
|
||
`git status`. It only got committed at all because a `.gitignore` edit was
|
||
staged alongside, which gave the scanner something to read.
|
||
|
||
**And a mistake worth recording rather than tidying away.** While proving the
|
||
fix, I committed a throwaway, then ran `git reset --hard HEAD~1` — which did not
|
||
undo the push the post-commit hook had already made, and silently discarded the
|
||
uncommitted hook fix I was in the middle of proving. The second proof then
|
||
failed for a reason that had nothing to do with the guard. Reconciled forward
|
||
rather than force-pushed. **Commit the work before proving the guard, not after.**
|
||
|
||
- **Closed:** #21, #22, #23 — and the `Batch 04 — Fertility` milestone.
|
||
- **Next action:** Batch 05 — Notifications. File its issues first; the milestone
|
||
is empty. The work is WorkManager-scheduled reminders (§29, §30) with the three
|
||
privacy modes from §28 — and the mode that matters is Discreet, already the
|
||
stored default and already chosen in onboarding, so this batch is about the
|
||
lock screen actually honouring it. **Pass F cannot be run until this lands**,
|
||
and it is the pass that checks the most likely real privacy breach in this
|
||
product.
|
||
- **Blockers:** Unchanged, both needing a person: the three branding marks (#8)
|
||
and the Command Center webhook (#9). The three standing QA gaps are also still
|
||
open and are getting more expensive the more UI exists — TalkBack has never
|
||
been run, text has never been scaled, and nothing has run at `minSdk` 26.
|
||
|
||
### 2026-08-18 — Batch 03 shipped: every screen the app has, and three defects only looking could find
|
||
|
||
All six Batch 03 issues closed and the milestone with them. Period stopped being
|
||
a data layer with a debug surface and became something you can actually use:
|
||
onboarding that ends in a forecast, a Today screen whose six states each say
|
||
something different and true, two-tap logging, a calendar, and Insights.
|
||
|
||
**Placeholder artwork, as §42 asks for it.** Every illustration and calendar
|
||
marker is a Compose vector path behind a replaceable name — no raster anywhere
|
||
under `src/`. The visual language is overlapping circular forms and nothing
|
||
else, because §42's forbidden list is a product decision: this app gets opened
|
||
in public and a glance over a shoulder should learn nothing. Note the deliberate
|
||
contrast with `docs/data/img/`, where a placeholder is still forbidden and #8
|
||
stays open; `docs/design/README.md` now explains why the two differ rather than
|
||
leaving it looking inconsistent.
|
||
|
||
**Three defects, none findable by reading:**
|
||
|
||
1. **Dark mode was broken for the whole of Batch 01.** `PeriodTheme` never
|
||
wrapped its content in a `Surface`, so any `Text` without an explicit colour
|
||
inherited Material's default — black — and the app's background never
|
||
painted. Light mode looked right by accident, because dark-on-cream is what
|
||
was wanted anyway. 129 tests were green throughout.
|
||
2. **"Period ended" appeared to do nothing.** The logic was correct: a period
|
||
that ends today still includes today, so the state genuinely does not change.
|
||
The screen was identical afterwards and the button read as broken, which is
|
||
worse than being broken somewhere visible.
|
||
3. **The today-underline collided with the spotting dot** on the one day that
|
||
was both. Invisible in the colour screenshot and obvious the moment it was
|
||
converted to greyscale — which is how §43 says to check, and why the check is
|
||
written that way rather than trusting that shapes differ because they were
|
||
designed to.
|
||
|
||
Two more came from tests doing their job: the typical-range quartiles were
|
||
indexed off `size` rather than `size - 1`, so a regular cycle with one long gap
|
||
was reported as "typical range 29–61 days" on the screen whose only job is to
|
||
say what has been learned; and Room's own executor meant a test's virtual clock
|
||
returned before the writes landed, which read as an empty database rather than
|
||
as a timing bug.
|
||
|
||
**The habit is now five pieces of work old and worth writing down as a rule: the
|
||
defects in this project are found by running it.** Three guards were green over
|
||
exactly what they claimed to check, dark mode was broken for a whole batch, and
|
||
a button looked broken while behaving correctly. None of it was visible in the
|
||
source.
|
||
|
||
- **Closed:** #15, #16, #17, #18, #19, #20 — and the `Batch 03 — Core UX`
|
||
milestone.
|
||
- **Next action:** Batch 04 — Fertility. File its issues first; the milestone is
|
||
empty. The work is estimated ovulation and the fertile window from the
|
||
forecast and a configurable luteal-phase assumption (§17, §18), the calendar
|
||
and Today states that already have their shapes and their `null` placeholders
|
||
waiting, and the not-contraception disclaimer everywhere fertility appears.
|
||
`CycleStatus` and `CalendarMarks` both already take fertility parameters and
|
||
currently receive null — Batch 04 is largely filling those in rather than
|
||
adding surfaces.
|
||
- **Blockers:** Unchanged. #8 needs the three branding marks drawn by a person.
|
||
#9 needs the Command Center webhook URL and secret, which are not readable
|
||
from this machine. Neither blocks Batch 04. Worth doing before much more UI:
|
||
run TalkBack once, scale the font to maximum once, and run the app once on a
|
||
device at `minSdk` 26 — all three are recorded as standing gaps and none has
|
||
ever been done.
|
||
|
||
### 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.
|