chore: rename the project to Privacy: Period Tracker
"Period" was always a working name — PRODUCT_PLAN.md §55 said so. The real
identity arrived with the brand guide and artwork, and this makes the project
call itself by it.
The repository is renamed in place on Forgejo (null/Period ->
null/Privacy-Period-Tracker), which keeps all 30 commits, all 27 issues, all 8
milestones and the four severity labels — verified by counting them on both
sides rather than assuming a rename is lossless. The git remote follows in the
same breath, so this commit's automatic push is what proves the new URL works.
WHAT DELIBERATELY DID NOT MOVE
"Period" is the product name AND the central domain word, and a mass rename
would have turned the data model into nonsense. PeriodRecord, PeriodWriteResult,
confirmPeriodStart, the period_records table and the button that says "Started
period" all describe a menstrual period rather than a product, and all 239
references to them are untouched.
So are the Kotlin package and applicationId. dev.privacyllc.period already reads
correctly under the new name — privacyllc is the company, period is the app —
and changing it would rewrite 68 files, rename the Room schema directory and
break the hardcoded path in schema-guard.sh for no gain. period.db stays for a
sharper reason: renaming a database file orphans the data on every device that
already has it.
Sixteen files changed, and the diff is small on purpose.
THE NAME HAS A SPACE AND THE ARTWORK DOES NOT
Canonical is "Privacy: Period Tracker". The supplied wordmark sets it without
one, so logo.webp and banner.webp now disagree with every document and with the
app itself. BRAND_GUIDE.md §10 is updated to the space form and says plainly
that the drawn mark has not caught up. Filed separately rather than papered
over, because closing that gap needs an artist and not a rename.
2026-08-18 15:54:34 -05:00
# Claude QA Coverage — Privacy: Period Tracker
chore: adopt the project template and add the Kotlin/Compose skeleton
Period was a bare directory holding one 2,527-line specification, with no git
repository, no tracker and no documentation convention. This is the adoption
from Projects/Template/START-HERE-New-Project.md, plus a project that compiles
so the hooks and future guards have something real to run against.
Documents. scaffold.sh created 19 paths, 0 skipped. The specification moved to
docs/planning/PRODUCT_PLAN.md unchanged in substance, with a status header; the
capitalised Docs/ is gone. Every scaffolded document was filled in for Period.
docs/OPERATIONS.md deleted — an offline app is not a deployed service.
DOC_TRUST_MAP.md written last, describing what is actually here, including what
this project deliberately does not have.
Code. Four Gradle modules. domain/cycle and domain/prediction are kotlin("jvm")
and cannot see the Android SDK, 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 explicitly not the
product; it exists so Batch 02's replacement can be shown to be better rather
than merely different.
Versions verified against their official sources today rather than inherited
from the specification's own numbers, which that document asks for: 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. AGP 9 ships Kotlin built in, so org.jetbrains.kotlin.android is no
longer applied. compileSdk is 37 because current AndroidX requires it; targetSdk
stays 36, Play's floor from 2026-08-31, and the difference is deliberate.
Six scripts taken into scripts/; the rest declined and named in docs/TOOLS.md.
Three hooks in .githooks/, with pre-commit adapted to Gradle.
closes #1
closes #2
2026-08-18 02:16:47 -05:00
```
Status: Current
Owner: _null
Last reviewed: 2026-08-18
Governs: what each QA pass actually reached
Review trigger: Any QA round run
```
> Pass by pass, what was reached and what was not. The point of this file is the
> **Blocked** and **Not run** rows: a pass left out of a report reads exactly
> like a pass that succeeded, and that is how untested code ships believing it
> was tested.
2026-08-18 15:33:04 -05:00
## Round 3 — 2026-08-18 at `0451fbe`, partial
Batches 04 and 05 landed: fertility estimates and the reminder system. Pass F
became runnable for the first time.
**Environment:** emulator `PeriodQA` , API 36, Pixel 6 profile, debug build, plus
docs: correct 57 claims the code and tracker disagreed with
Every document in the tree audited against the source, the tracker and git
history, each finding then given to a second reader tasked with refuting it.
74 raised, 12 refuted, 57 applied. No code changed.
THE README DESCRIBED A SKELETON
Its Status table — the one place a claim about what is built is allowed to live
— still read "there is no usable app yet", with Not built against Room, the four
core screens, fertility and notifications, and No round run against QA. Five
batches had shipped and three QA rounds had run.
TWO DOCUMENTS WERE SILENTLY NEVER FIRING
architecture/README.md and design/README.md wrote Governs: as prose ("the Gradle
module graph", "the design tokens in core/designsystem"). Neither contains a
path token, so doc-triggers.py reduced them to globs matching nothing, and one
real glob apiece made them look path-governing rather than subject-governing —
the state the script's own header calls invisible. Editing a Room entity never
fired the document owning the migration table. Both now fire, proved by running
the script.
SECURITY.md CLAIMED FOUR UNBUILT PROTECTIONS
App lock listed among what works offline; biometric/PIN gating described as
protecting app launch; the incognito launcher as existing; Play Billing in the
third parties table without the "not yet integrated" marker its neighbours
carry. All are Batch 06/07 work.
The advertising boundary was overstated in SECURITY.md and the README alike:
both said the ads module declares no dependency and a guard proves it. There is
no ads module. The pre-declared ":core:ads" to emptySet() rule is stricter than
the sentence it replaced and matches nothing until Batch 07, which is why the
guard is proved by injection rather than trusted.
SMALLER, EACH A REAL TRAP
WORK_CYCLE.md pointed at docs/architecture/scripts/forgejo-issue.py, a template
path absent here — missed by doc-claims.sh, which reads backticked prose and not
fenced blocks. ClaudeReport.md's Round notes said "No rounds yet" after three
rounds because ClaudeQAPlan.md's after-a-round list never named that section;
the playbook is fixed first. The instrumented-test count was eight in three
places and is four. HISTORY.md said the repository had no code and that nothing
had been tried and dropped, when three approaches had.
DELIBERATELY UNCHANGED
ClaudeReport.md's last verified build SHA stays at 0451fbe — no round has run
since, and moving it would claim a verification nobody performed. Every
DEVELOPMENT_LOG entry stays as written.
Guards: ./gradlew check, schema-guard.sh, doc-claims.sh (235 claimed paths, all
present), doc-triggers.py, and a link sweep over 21 markdown files.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-18 16:33:14 -05:00
6 instrumented tests on the same device — 4 in `NotificationPrivacyTest` , 2 in
`PeriodCrudTest` .
2026-08-18 15:33:04 -05:00
| Pass | Result | Notes |
| --- | --- | --- |
| A — First run | **Pass** | Re-run after the brand change; onboarding reaches a forecast, relaunch skips it. |
| B — Core loop | **Pass** | Unchanged and re-driven. Fertility appears on Today and the calendar once the forecast is tight enough. |
| C — Failure paths | **Partial** | As Round 2. Airplane mode, denied notification permission and a killed process still untried. |
| D — Persistence | **Partial** | As Round 2. Reboot and update-over-install untried — the second matters more now that WorkManager holds scheduled work. |
| E — Forecast under hard histories | **Partial** | Unit-tested against both engines; the §51 histories still have not been entered by hand. |
docs: correct 57 claims the code and tracker disagreed with
Every document in the tree audited against the source, the tracker and git
history, each finding then given to a second reader tasked with refuting it.
74 raised, 12 refuted, 57 applied. No code changed.
THE README DESCRIBED A SKELETON
Its Status table — the one place a claim about what is built is allowed to live
— still read "there is no usable app yet", with Not built against Room, the four
core screens, fertility and notifications, and No round run against QA. Five
batches had shipped and three QA rounds had run.
TWO DOCUMENTS WERE SILENTLY NEVER FIRING
architecture/README.md and design/README.md wrote Governs: as prose ("the Gradle
module graph", "the design tokens in core/designsystem"). Neither contains a
path token, so doc-triggers.py reduced them to globs matching nothing, and one
real glob apiece made them look path-governing rather than subject-governing —
the state the script's own header calls invisible. Editing a Room entity never
fired the document owning the migration table. Both now fire, proved by running
the script.
SECURITY.md CLAIMED FOUR UNBUILT PROTECTIONS
App lock listed among what works offline; biometric/PIN gating described as
protecting app launch; the incognito launcher as existing; Play Billing in the
third parties table without the "not yet integrated" marker its neighbours
carry. All are Batch 06/07 work.
The advertising boundary was overstated in SECURITY.md and the README alike:
both said the ads module declares no dependency and a guard proves it. There is
no ads module. The pre-declared ":core:ads" to emptySet() rule is stricter than
the sentence it replaced and matches nothing until Batch 07, which is why the
guard is proved by injection rather than trusted.
SMALLER, EACH A REAL TRAP
WORK_CYCLE.md pointed at docs/architecture/scripts/forgejo-issue.py, a template
path absent here — missed by doc-claims.sh, which reads backticked prose and not
fenced blocks. ClaudeReport.md's Round notes said "No rounds yet" after three
rounds because ClaudeQAPlan.md's after-a-round list never named that section;
the playbook is fixed first. The instrumented-test count was eight in three
places and is four. HISTORY.md said the repository had no code and that nothing
had been tried and dropped, when three approaches had.
DELIBERATELY UNCHANGED
ClaudeReport.md's last verified build SHA stays at 0451fbe — no round has run
since, and moving it would claim a verification nobody performed. Every
DEVELOPMENT_LOG entry stays as written.
Guards: ./gradlew check, schema-guard.sh, doc-claims.sh (235 claimed paths, all
present), doc-triggers.py, and a link sweep over 21 markdown files.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-18 16:33:14 -05:00
| F — Notification privacy | **Partial, and this is the important row** | Four instrumented tests on a device assert what a lock screen *would* render: every kind × both private modes attaches a public version and is marked `VISIBILITY_PRIVATE` , no public title or body carries a health word, Direct is the only mode marked public, no channel name in any of the three modes carries one either, and Discreet cannot pop over the screen. Action labels are checked by the JVM unit tests in `NotificationCopyTest` , not on the device. **Nobody has yet looked at an actual locked screen.** That is a real gap: the tests check the notification object, and the last mile is what the system chooses to draw. |
2026-08-18 15:33:04 -05:00
| G — Accessibility | **Partial** | Unchanged. Calendar verified in real greyscale again after the palette change. TalkBack and font scaling still never run. |
| H — Data ownership | **Partial** | Unchanged. |
### What this round found
Two defects, both in how Android behaves rather than in the app's logic, and
both found by running on a device:
1. **A notification channel is immutable after creation.** Importance and
lock-screen visibility cannot be changed once set, so a single shared channel
would have kept whatever the user's first privacy mode chose — switching from
Direct to Maximum privacy would have appeared to work and changed nothing.
One channel per mode now.
2. ** `checkPermissions` was green over its own target**, reading a stale merged
manifest because it did not depend on the task that writes one. The third
guard in this project to fail its first proof.
Also confirmed: a seventeen-day "fertile window" was on screen for a user one
cycle in. Arithmetically correct, useless, and only visible by looking.
2026-08-18 04:05:58 -05:00
## Round 2 — 2026-08-18 at `19edf4c`, partial
Batch 03 built every screen the app has, and each was driven by hand on the
emulator as it landed rather than in one pass at the end. Same environment:
`PeriodQA` , API 36, Pixel 6 profile, debug build.
| Pass | Result | Notes |
| --- | --- | --- |
| A — First run | **Pass** | Clean install reaches onboarding; all seven screens walked; the flow produces a forecast and a relaunch goes straight to Today. |
| B — Core loop | **Pass** | Two-tap logging confirmed on the device: "Started period" → "Yes — today" → "Logged ✓". Editing, ending, and spotting reclassification all exercised. |
| C — Failure paths | **Partial** | Duplicate-date logging, an end date before a start, and a future date in the picker all handled. Airplane mode, denied permissions and a process killed mid-write still untried. |
| D — Persistence | **Partial** | Onboarding completion, records and settings all survive force-stop and relaunch. Reboot and update-over-install untried. |
| E — Forecast under hard histories | **Partial** | The §51 cases pass as unit tests against both engines, and the not-yet path was driven through the UI. The variable and outlier histories have still not been entered by hand. |
| F — Notification privacy | **Not run** | Nothing sends a notification yet — Batch 05. The onboarding *choice* was verified: Discreet is selected before the user touches anything. |
| G — Accessibility | **Partial** | Every calendar day, the confidence indicator and the hero countdown carry content descriptions, checked by reading the view hierarchy. **The calendar was verified in actual greyscale** and all five marks remain distinguishable. TalkBack itself, and font scaling, still untried. |
| H — Data ownership | **Partial** | Delete-all covered by an instrumented test. Export, app lock and artifact inspection do not exist yet. |
### What driving it found that tests did not
Three defects, none of which any unit test would have caught:
1. **Dark mode was broken for the whole of Batch 01.** `PeriodTheme` never
wrapped its content in a `Surface` , so text without an explicit colour
inherited black and the app background never painted. Light mode looked
correct by accident.
2. ** "Period ended" appeared to do nothing.** The logic was right — a period
ending today still includes today — but the screen was identical afterwards,
so the button read as broken.
3. **Today's underline collided with the spotting dot** , on the one day that was
both. Invisible in the colour screenshot, obvious in greyscale.
docs: correct 57 claims the code and tracker disagreed with
Every document in the tree audited against the source, the tracker and git
history, each finding then given to a second reader tasked with refuting it.
74 raised, 12 refuted, 57 applied. No code changed.
THE README DESCRIBED A SKELETON
Its Status table — the one place a claim about what is built is allowed to live
— still read "there is no usable app yet", with Not built against Room, the four
core screens, fertility and notifications, and No round run against QA. Five
batches had shipped and three QA rounds had run.
TWO DOCUMENTS WERE SILENTLY NEVER FIRING
architecture/README.md and design/README.md wrote Governs: as prose ("the Gradle
module graph", "the design tokens in core/designsystem"). Neither contains a
path token, so doc-triggers.py reduced them to globs matching nothing, and one
real glob apiece made them look path-governing rather than subject-governing —
the state the script's own header calls invisible. Editing a Room entity never
fired the document owning the migration table. Both now fire, proved by running
the script.
SECURITY.md CLAIMED FOUR UNBUILT PROTECTIONS
App lock listed among what works offline; biometric/PIN gating described as
protecting app launch; the incognito launcher as existing; Play Billing in the
third parties table without the "not yet integrated" marker its neighbours
carry. All are Batch 06/07 work.
The advertising boundary was overstated in SECURITY.md and the README alike:
both said the ads module declares no dependency and a guard proves it. There is
no ads module. The pre-declared ":core:ads" to emptySet() rule is stricter than
the sentence it replaced and matches nothing until Batch 07, which is why the
guard is proved by injection rather than trusted.
SMALLER, EACH A REAL TRAP
WORK_CYCLE.md pointed at docs/architecture/scripts/forgejo-issue.py, a template
path absent here — missed by doc-claims.sh, which reads backticked prose and not
fenced blocks. ClaudeReport.md's Round notes said "No rounds yet" after three
rounds because ClaudeQAPlan.md's after-a-round list never named that section;
the playbook is fixed first. The instrumented-test count was eight in three
places and is four. HISTORY.md said the repository had no code and that nothing
had been tried and dropped, when three approaches had.
DELIBERATELY UNCHANGED
ClaudeReport.md's last verified build SHA stays at 0451fbe — no round has run
since, and moving it would claim a verification nobody performed. Every
DEVELOPMENT_LOG entry stays as written.
Guards: ./gradlew check, schema-guard.sh, doc-claims.sh (235 claimed paths, all
present), doc-triggers.py, and a link sweep over 21 markdown files.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-18 16:33:14 -05:00
The pattern is now two rounds old and worth stating as a rule: **the defects in
2026-08-18 04:05:58 -05:00
this project are found by opening it, not by reading it.**
2026-08-18 03:01:33 -05:00
## Round 1 — 2026-08-18 at `adc5075`, partial
chore: adopt the project template and add the Kotlin/Compose skeleton
Period was a bare directory holding one 2,527-line specification, with no git
repository, no tracker and no documentation convention. This is the adoption
from Projects/Template/START-HERE-New-Project.md, plus a project that compiles
so the hooks and future guards have something real to run against.
Documents. scaffold.sh created 19 paths, 0 skipped. The specification moved to
docs/planning/PRODUCT_PLAN.md unchanged in substance, with a status header; the
capitalised Docs/ is gone. Every scaffolded document was filled in for Period.
docs/OPERATIONS.md deleted — an offline app is not a deployed service.
DOC_TRUST_MAP.md written last, describing what is actually here, including what
this project deliberately does not have.
Code. Four Gradle modules. domain/cycle and domain/prediction are kotlin("jvm")
and cannot see the Android SDK, 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 explicitly not the
product; it exists so Batch 02's replacement can be shown to be better rather
than merely different.
Versions verified against their official sources today rather than inherited
from the specification's own numbers, which that document asks for: 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. AGP 9 ships Kotlin built in, so org.jetbrains.kotlin.android is no
longer applied. compileSdk is 37 because current AndroidX requires it; targetSdk
stays 36, Play's floor from 2026-08-31, and the difference is deliberate.
Six scripts taken into scripts/; the rest declined and named in docs/TOOLS.md.
Three hooks in .githooks/, with pre-commit adapted to Gradle.
closes #1
closes #2
2026-08-18 02:16:47 -05:00
2026-08-18 03:01:33 -05:00
Not a full round. Batch 01 produced the first build, and this covered the two
passes that build could support. Recorded as partial rather than left out,
because a pass omitted from a report reads exactly like a pass that succeeded.
chore: adopt the project template and add the Kotlin/Compose skeleton
Period was a bare directory holding one 2,527-line specification, with no git
repository, no tracker and no documentation convention. This is the adoption
from Projects/Template/START-HERE-New-Project.md, plus a project that compiles
so the hooks and future guards have something real to run against.
Documents. scaffold.sh created 19 paths, 0 skipped. The specification moved to
docs/planning/PRODUCT_PLAN.md unchanged in substance, with a status header; the
capitalised Docs/ is gone. Every scaffolded document was filled in for Period.
docs/OPERATIONS.md deleted — an offline app is not a deployed service.
DOC_TRUST_MAP.md written last, describing what is actually here, including what
this project deliberately does not have.
Code. Four Gradle modules. domain/cycle and domain/prediction are kotlin("jvm")
and cannot see the Android SDK, 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 explicitly not the
product; it exists so Batch 02's replacement can be shown to be better rather
than merely different.
Versions verified against their official sources today rather than inherited
from the specification's own numbers, which that document asks for: 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. AGP 9 ships Kotlin built in, so org.jetbrains.kotlin.android is no
longer applied. compileSdk is 37 because current AndroidX requires it; targetSdk
stays 36, Play's floor from 2026-08-31, and the difference is deliberate.
Six scripts taken into scripts/; the rest declined and named in docs/TOOLS.md.
Three hooks in .githooks/, with pre-commit adapted to Gradle.
closes #1
closes #2
2026-08-18 02:16:47 -05:00
2026-08-18 03:01:33 -05:00
**Environment:** emulator `PeriodQA` , API 36 (`sdk_gphone64_x86_64`), Pixel 6
profile, debug build.
chore: adopt the project template and add the Kotlin/Compose skeleton
Period was a bare directory holding one 2,527-line specification, with no git
repository, no tracker and no documentation convention. This is the adoption
from Projects/Template/START-HERE-New-Project.md, plus a project that compiles
so the hooks and future guards have something real to run against.
Documents. scaffold.sh created 19 paths, 0 skipped. The specification moved to
docs/planning/PRODUCT_PLAN.md unchanged in substance, with a status header; the
capitalised Docs/ is gone. Every scaffolded document was filled in for Period.
docs/OPERATIONS.md deleted — an offline app is not a deployed service.
DOC_TRUST_MAP.md written last, describing what is actually here, including what
this project deliberately does not have.
Code. Four Gradle modules. domain/cycle and domain/prediction are kotlin("jvm")
and cannot see the Android SDK, 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 explicitly not the
product; it exists so Batch 02's replacement can be shown to be better rather
than merely different.
Versions verified against their official sources today rather than inherited
from the specification's own numbers, which that document asks for: 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. AGP 9 ships Kotlin built in, so org.jetbrains.kotlin.android is no
longer applied. compileSdk is 37 because current AndroidX requires it; targetSdk
stays 36, Play's floor from 2026-08-31, and the difference is deliberate.
Six scripts taken into scripts/; the rest declined and named in docs/TOOLS.md.
Three hooks in .githooks/, with pre-commit adapted to Gradle.
closes #1
closes #2
2026-08-18 02:16:47 -05:00
| Pass | Result | Notes |
| --- | --- | --- |
2026-08-18 03:01:33 -05:00
| A — First run | **Pass** | Clean install, cold start, empty state reads correctly ("No forecast yet", "No periods logged yet"), all four tabs reachable. No notification permission prompt yet — none is requested until Batch 05. |
| B — Core loop | **Pass** | Logged a period; cycle day, countdown, window and confidence appeared. Edited the start date twice; forecast moved from 15 Sep to 13 Sep and the record was marked `edited` . Deletion covered by instrumented test rather than by hand. |
| C — Failure paths | **Partial** | Only one path exercised, and it found a crash — see below. Airplane mode, denied permissions and a killed process mid-entry were not tried. |
| D — Persistence and migration | **Partial** | Covered by an instrumented test that closes and reopens a file-backed database, which is what a force-stop does. A real force-stop, a reboot and an update-over-install were not tried. |
| E — Forecast under hard histories | **Not run** | The §51 cases pass as unit tests; none has been driven through the UI, which is what this pass is for. |
| F — Notification privacy on a lock screen | **Not run** | Nothing sends a notification yet — Batch 05. |
| G — Accessibility | **Not run** | TalkBack, font scaling and greyscale legibility untried. The working surface is not the designed screen, so this is worth deferring to Batch 03 rather than testing a screen that is about to be replaced. |
| H — Data ownership and leakage | **Partial** | Delete-all covered by an instrumented test. Export, app lock and the built-artifact inspection do not exist yet. |
### What pass C found
Tapping **Started today** twice on the same day killed the app:
```text
FATAL EXCEPTION: main
android.database.sqlite.SQLiteConstraintException: UNIQUE constraint failed:
period_records.startDate
```
Not filed as an issue: it was found and fixed inside the same batch, before any
build left this repository, and a tracker issue closed by the commit that
introduced the code would be bookkeeping rather than a record. It is written
down here, in the commit that fixed it, and in
[`../architecture/README.md` ](../architecture/README.md ), with four regression
tests pinning the behaviour.
The interesting part is not the crash. It is that **the first thing tried by
hand broke immediately**, on the app's primary button, with 70 unit tests
green — which is the argument for pass C existing at all.
### What lint found, that no pass would have
Wiring the boundary guard into `./gradlew check` ran Android lint for the first
time, on `f5e9fbe` :
```text
NewApi: java.time.LocalDate#ofInstant requires API 34 (minSdk is 26)
NewApi: java.time.LocalDate#EPOCH requires API 34 (minSdk is 26)
```
Both on the recalculation path — a crash on every device below Android 14.
Invisible to the unit tests and invisible to an API 36 emulator. Fixed, and
worth recording as a standing gap below: **this project has no test on a device
at its own `minSdk` .**
chore: adopt the project template and add the Kotlin/Compose skeleton
Period was a bare directory holding one 2,527-line specification, with no git
repository, no tracker and no documentation convention. This is the adoption
from Projects/Template/START-HERE-New-Project.md, plus a project that compiles
so the hooks and future guards have something real to run against.
Documents. scaffold.sh created 19 paths, 0 skipped. The specification moved to
docs/planning/PRODUCT_PLAN.md unchanged in substance, with a status header; the
capitalised Docs/ is gone. Every scaffolded document was filled in for Period.
docs/OPERATIONS.md deleted — an offline app is not a deployed service.
DOC_TRUST_MAP.md written last, describing what is actually here, including what
this project deliberately does not have.
Code. Four Gradle modules. domain/cycle and domain/prediction are kotlin("jvm")
and cannot see the Android SDK, 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 explicitly not the
product; it exists so Batch 02's replacement can be shown to be better rather
than merely different.
Versions verified against their official sources today rather than inherited
from the specification's own numbers, which that document asks for: 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. AGP 9 ships Kotlin built in, so org.jetbrains.kotlin.android is no
longer applied. compileSdk is 37 because current AndroidX requires it; targetSdk
stays 36, Play's floor from 2026-08-31, and the difference is deliberate.
Six scripts taken into scripts/; the rest declined and named in docs/TOOLS.md.
Three hooks in .githooks/, with pre-commit adapted to Gradle.
closes #1
closes #2
2026-08-18 02:16:47 -05:00
## Standing gaps
Things no round has ever covered, carried forward until they are. This list
existing is not a failure; it not existing while the gaps do is.
2026-08-18 15:33:04 -05:00
- **No lock screen has been looked at.** Pass F's instrumented tests assert the
notification object is built correctly, which is most of the risk and not all
of it. What the system actually draws on a locked device — including heads-up
behaviour and any launcher's own preview — has never been seen by anybody.
2026-08-18 04:05:58 -05:00
- **TalkBack has never actually been run.** Content descriptions are written and
were checked by reading the view hierarchy, which proves they exist and not
that they make sense in sequence. Pass G is not complete until somebody
navigates a screen with their eyes shut.
- **Font scaling untried.** The hero number is 72sp; at the largest accessibility
scale it may not fit beside anything.
docs: record the minSdk emulator, and the first run at API 26
A second AVD exists — PeriodMinSdk26, API 26, Pixel 6 — and the app was built,
installed and driven on it end to end. Everything before today ran on API 36,
which the standing gaps list has said since Batch 01.
The QA plan now names both emulators, because rounds cite PeriodQA by name and
nothing defined it. It also carries the recreate command and three mechanical
traps that each cost time today:
- a windowed emulator dies with its session's X server (XIO: fatal IO error on
X server ":0") and took one run down mid-flow; -no-window has no such
dependency and screencap still works
- adb regularly has three devices attached on this machine, since other projects
keep emulators running, so the serial must be resolved from the AVD name
rather than assumed to be emulator-5554
- the debug build's application id carries a .debug suffix, so
`monkey -p dev.privacyllc.period` aborts with "No activities found to run"
after a successful install
The coverage doc's "No device at minSdk" gap is marked partly closed, with what
was reached (onboarding to first forecast, the date picker, all four tabs, a
relaunch after a cold boot) and what was not (notifications, the instrumented
suites, anything needing more than one cycle of history).
Also fixes a guard failure I introduced in ad085fb: that commit's log entry
named docs/architecture/scripts/forgejo-issue.py in backticks, and doc-claims.sh
reads a backticked path as a claim the file exists. I had run the guard before
writing the entry and not after. Named bare now, per the convention
DOC_TRUST_MAP.md already documents; doc-claims reports 255 paths, all present.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-18 18:07:07 -05:00
- **~~No device at `minSdk` ~~ — partly closed on 2026-08-18 at `0d280d5` .** The
app has now been built, installed and driven on `PeriodMinSdk26` , an API 26
emulator: onboarding start to first forecast, the Material 3 date picker,
Today, Calendar, Insights and Settings, plus a relaunch after the emulator was
killed and cold-booted. No crash, and no `NoSuchMethodError` , `VerifyError` or
`NoClassDefFoundError` attributable to the app — the only `NoClassDefFoundError`
in logcat belongs to `com.google.android.googlequicksearchbox` . What is still
unreached at API 26: notifications and their lock-screen rendering, the
instrumented suites, and anything needing more than one cycle of history, so
fertility never became visible. Passes C, E, F, G and H have never run at this
API level.
chore: adopt the project template and add the Kotlin/Compose skeleton
Period was a bare directory holding one 2,527-line specification, with no git
repository, no tracker and no documentation convention. This is the adoption
from Projects/Template/START-HERE-New-Project.md, plus a project that compiles
so the hooks and future guards have something real to run against.
Documents. scaffold.sh created 19 paths, 0 skipped. The specification moved to
docs/planning/PRODUCT_PLAN.md unchanged in substance, with a status header; the
capitalised Docs/ is gone. Every scaffolded document was filled in for Period.
docs/OPERATIONS.md deleted — an offline app is not a deployed service.
DOC_TRUST_MAP.md written last, describing what is actually here, including what
this project deliberately does not have.
Code. Four Gradle modules. domain/cycle and domain/prediction are kotlin("jvm")
and cannot see the Android SDK, 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 explicitly not the
product; it exists so Batch 02's replacement can be shown to be better rather
than merely different.
Versions verified against their official sources today rather than inherited
from the specification's own numbers, which that document asks for: 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. AGP 9 ships Kotlin built in, so org.jetbrains.kotlin.android is no
longer applied. compileSdk is 37 because current AndroidX requires it; targetSdk
stays 36, Play's floor from 2026-08-31, and the difference is deliberate.
Six scripts taken into scripts/; the rest declined and named in docs/TOOLS.md.
Three hooks in .githooks/, with pre-commit adapted to Gradle.
closes #1
closes #2
2026-08-18 02:16:47 -05:00
- **Physical-device coverage is undecided.** Passes F and G need a real device
with a lock screen and TalkBack; which device that is has not been chosen, and
an emulator is not a substitute for either.
- **Long-horizon accuracy** — whether predictions measurably improve at 3, 6 and
12 confirmed cycles — cannot be reached by a QA round at all. It needs either
a simulated history harness or real elapsed time, and until one exists the
product's headline claim is tested only at the unit level.