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
|
|
|
|
# Design
|
|
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
Status: Current
|
|
|
|
|
|
Owner: _null
|
|
|
|
|
|
Last reviewed: 2026-08-18
|
|
|
|
|
|
Governs: docs/design/**, the design tokens in core/designsystem, and the product's
|
|
|
|
|
|
tone and interface copy
|
|
|
|
|
|
Review trigger: Any new user-facing screen or state; any change to the colour or
|
|
|
|
|
|
type tokens; any change to notification copy or to a privacy or
|
|
|
|
|
|
fertility disclaimer
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
## Where the detail is
|
|
|
|
|
|
|
|
|
|
|
|
The screen-by-screen specification and the actual words are
|
|
|
|
|
|
[`../planning/PRODUCT_PLAN.md`](../planning/PRODUCT_PLAN.md), and they are not
|
|
|
|
|
|
repeated here — a second copy of interface copy is how two versions of a
|
|
|
|
|
|
disclaimer come to exist.
|
|
|
|
|
|
|
|
|
|
|
|
| Subject | Section it owns |
|
|
|
|
|
|
| --- | --- |
|
|
|
|
|
|
| Onboarding, seven screens, with copy | §19, §56 |
|
|
|
|
|
|
| Navigation — four tabs | §20 |
|
|
|
|
|
|
| Today screen and its six dynamic states | §21, §22 |
|
|
|
|
|
|
| Period logging, period end, spotting | §23, §24, §25 |
|
|
|
|
|
|
| Calendar states and markers | §26 |
|
|
|
|
|
|
| Insights | §27 |
|
|
|
|
|
|
| Notification modes, types and flow | §28, §29, §30 |
|
|
|
|
|
|
| Incognito launcher | §32 |
|
|
|
|
|
|
| Look and feel, visual direction, colour, typography, motion | §37–§41 |
|
|
|
|
|
|
| Artwork | §42 |
|
|
|
|
|
|
| Accessibility | §43 |
|
|
|
|
|
|
|
|
|
|
|
|
This document holds what governs those: the tone, and the rules that decide an
|
|
|
|
|
|
argument the specification did not anticipate.
|
|
|
|
|
|
|
|
|
|
|
|
## Tone
|
|
|
|
|
|
|
|
|
|
|
|
Calm, private, intelligent, adult. The app is a good utility with warmth — it is
|
|
|
|
|
|
not clinical, not childish, not gamified, and not stereotypically feminine as an
|
|
|
|
|
|
identity. It never celebrates a period and never alarms about one.
|
|
|
|
|
|
|
|
|
|
|
|
One paragraph, and it settles most small arguments: **the app speaks like
|
|
|
|
|
|
someone competent who is not making a fuss.** "Your period is late!" is out.
|
|
|
|
|
|
"Not yet?" with an updated forecast is in.
|
|
|
|
|
|
|
|
|
|
|
|
## Four rules that decide the arguments
|
|
|
|
|
|
|
|
|
|
|
|
**1. The number is the hero.** The forecast dominates the Today screen —
|
|
|
|
|
|
[§38](../planning/PRODUCT_PLAN.md). Anything competing with it for attention is
|
|
|
|
|
|
wrong, including anything of ours.
|
|
|
|
|
|
|
|
|
|
|
|
**2. Nothing asserts certainty the model does not have.** A window and a
|
|
|
|
|
|
confidence label, never a bare exact date presented as fact. "Estimated
|
|
|
|
|
|
ovulation", never "you are ovulating today". Fertility copy carries the
|
|
|
|
|
|
not-contraception line wherever it appears.
|
|
|
|
|
|
|
|
|
|
|
|
**3. State is never colour alone.** Confirmed period is a solid fill, predicted
|
|
|
|
|
|
is dotted or outlined, fertile window is a ring, ovulation is its own small
|
|
|
|
|
|
marker — distinguishable in greyscale, because that is also what makes them
|
|
|
|
|
|
distinguishable to a colourblind user and to a screenshot in a bug report.
|
|
|
|
|
|
Predicted and confirmed days must never look identical.
|
|
|
|
|
|
|
|
|
|
|
|
**4. Ads never touch a health action.** No banner in onboarding, in the
|
|
|
|
|
|
period-start confirmation, in the period-end confirmation, or between steps of a
|
|
|
|
|
|
health workflow — and never an interstitial after logging
|
|
|
|
|
|
([§33](../planning/PRODUCT_PLAN.md)). Banner space is reserved in the layout so a
|
|
|
|
|
|
failed ad does not move the content.
|
|
|
|
|
|
|
|
|
|
|
|
## Colour, in one line each
|
|
|
|
|
|
|
|
|
|
|
|
The palette is [§39](../planning/PRODUCT_PLAN.md); the constraints on it are:
|
|
|
|
|
|
|
|
|
|
|
|
- **Not pink as the whole identity.** Deep plum, muted berry, soft lavender,
|
|
|
|
|
|
warm cream, charcoal, muted sage/teal.
|
|
|
|
|
|
- **Period state** is a sophisticated berry or plum — never graphic blood-red.
|
|
|
|
|
|
- **Fertile window** is muted teal or sage — never bright green, which reads as
|
|
|
|
|
|
*safe* and this app must never say that.
|
|
|
|
|
|
- Everything goes through Material 3 colour roles and centralized tokens in
|
|
|
|
|
|
`core/designsystem`. **No hard-coded colours in a Composable** — the guard is
|
|
|
|
|
|
that a colour literal outside the token file is a review failure.
|
|
|
|
|
|
|
|
|
|
|
|
## The states most often left undesigned
|
|
|
|
|
|
|
|
|
|
|
|
Designed here on purpose, because they are the two most people meet first:
|
|
|
|
|
|
|
|
|
|
|
|
- **Empty** — no periods logged yet. It has to make the next action obvious
|
|
|
|
|
|
rather than apologise.
|
|
|
|
|
|
- **Learning** — one or two cycles recorded. The app says it is still learning
|
|
|
|
|
|
rather than showing a confident forecast it has not earned. "Getting to know
|
|
|
|
|
|
your pattern", not a percentage.
|
|
|
|
|
|
|
feat: placeholder vector artwork, and calendar markers that work in greyscale
Compose vector paths in core/designsystem/art — no raster anywhere under src/,
so everything stays crisp at any density and adds nothing to the APK.
The four calendar markers differ in SHAPE, not only colour: a solid disc for a
confirmed period, a dotted outline for a predicted one, a continuous ring for
the fertile window, and a four-pointed mark for ovulation. §26 and §43 both
require it and for the same reason — a calendar whose states differ only in
colour carries no information for a colourblind user, none in greyscale, and
none in the bug report somebody files about it. Predicted is deliberately not a
lighter confirmed: opacity alone fails exactly when the screen is dim.
Illustrations for welcome, empty state, learning and the privacy promise, plus
the cycle progress mark for Today. The visual language is overlapping circular
forms and nothing else — §42's forbidden list is a product decision, not
squeamishness: this app gets opened in public and a glance over a shoulder
should learn nothing.
Placeholders here and never in docs/data/img, which docs/design/README.md now
explains rather than leaving as an apparent inconsistency: §42 asks for these
explicitly, a replaceable name keeps them replaceable, and a screen with no
illustration cannot be evaluated while a project card with no icon just shows
initials.
closes #15
2026-08-18 03:35:04 -05:00
|
|
|
|
## The artwork is placeholder, and says so here
|
|
|
|
|
|
|
|
|
|
|
|
Every illustration and calendar marker in the app is a **Compose vector path**
|
|
|
|
|
|
in `core/designsystem/.../art/`, written as PRODUCT_PLAN.md §42 asks: polished
|
|
|
|
|
|
placeholders behind replaceable names, so real artwork changes one function body
|
|
|
|
|
|
and no call site.
|
|
|
|
|
|
|
|
|
|
|
|
The visual language is deliberately narrow — **overlapping circular forms** and
|
|
|
|
|
|
nothing else. §42's forbidden list (blood drops, tampons, pads, uterus imagery,
|
|
|
|
|
|
gender symbols, anatomy) is a product decision rather than squeamishness: this
|
|
|
|
|
|
app gets opened in public, and somebody glancing over a shoulder should learn
|
|
|
|
|
|
nothing. That is a property of the pictures as much as of the notification text.
|
|
|
|
|
|
|
|
|
|
|
|
**Note the deliberate inconsistency with `docs/data/img/`,** where a placeholder
|
|
|
|
|
|
is forbidden. The rule there is that an image which looks finished outlives the
|
|
|
|
|
|
issue that would have replaced it, because nobody files a ticket against
|
|
|
|
|
|
something that appears done. In-app assets escape that because §42 asks for them
|
|
|
|
|
|
explicitly and the replaceable-name mechanism is what keeps them replaceable —
|
|
|
|
|
|
and because a screen with no illustration cannot be evaluated at all, while a
|
|
|
|
|
|
project card with no icon simply shows initials.
|
|
|
|
|
|
|
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
|
|
|
|
## Include the rejected version
|
|
|
|
|
|
|
|
|
|
|
|
For any decision that was genuinely close, record what was not chosen and why.
|
|
|
|
|
|
Without it the same option is proposed again every few months and re-argued from
|
|
|
|
|
|
nothing. The first entries belong to whoever makes those calls; the
|
|
|
|
|
|
specification's `Avoid:` lists are already a partial record of them.
|
|
|
|
|
|
|
|
|
|
|
|
## What does not belong here
|
|
|
|
|
|
|
|
|
|
|
|
- How it is built — [`../architecture/README.md`](../architecture/README.md)
|
|
|
|
|
|
- Scope and audience — [`../planning/PROJECT_PLAN.md`](../planning/PROJECT_PLAN.md)
|