Roadmap
Sequence and status only. The reasoning behind a decision lives in docs/adr/; the words
live in CONTEXT.md; commands, layout and house rules live in
CLAUDE.md.
This file is the record, not the worklist. What is still open — the boxes to tick, with the
device state and the commands to read it — lives in DOD.md, which stays short enough to
open every session. Read the phase you are in here; read DOD.md to know what to do next.
A phase being planned or built gets its own file — phase-N.md beside this one — so working on
it costs the phase rather than the whole history. This file stays deliberately thin: one paragraph
per phase, no task lists. The detail is written when the phase opens, not now. The app this template
was extracted from and the first app built from it settled on exactly this split independently.
phase-template.md is the shape a phase file settled into; copy it on the day.
Status
- Phase 0 (
phase-0.md) — Skeleton: Items, photos, reminders, Room and the custom backup agent stripped, platform Auto Backup in their place; one home screen holding the Listening switch’s place; Darek’s palette; the app opens in Polish on first launch (ADR-0012). ADR-0001, 0002, 0003 and 0005 get their amendments as their modules go. 2026-10-31: checkpoints A–E done, every reading taken; reading 3 on both phones (phase-0.md, Taken 2026-10-31) - Phase P — The pipeline: repository setup, upload key, Play entry, secrets, a green internal upload
- Phase 1 (
phase-1.md) — The mechanism on the phone, app open: Słuchaj on Home, the bundled model hears “Hej Darek, powiedz coś” from a real voice and Darek answers „Co tam, mordo?” offline in his own bundled voice (ADR-0009, ADR-0013, ADR-0014), with the microphone disclosure; the Gate call with its cancel window in the debug build only (ADR-0011). Readings: real voices at a distance and in a car, the offline Polish voice, the dial on the owner’s phone - Phase D (
phase-D.md) — Daria (ADR-0016), after 0.4.0 and before phase 2: the Persona in Settings, gosia bundled, the name and its Decoys per Persona with the correction, the built-in Commands’ phrases without the name and every line in both genders, the UI strings that name the Persona as pairs. Readings: her voice on the test phone, her name and Decoys on the owner’s voice; the rest is owed before she can call (DOD.md) - Phase 2 (
phase-2.md) — Listening: the microphone service, its notification, the start paths (switch, notification after boot, Quick Settings tile), pausing during the Reply and the call, MIUI setup, a night’s battery reading (ADR-0010). „Hej” in front of the name for every answer that is not a Command, and a negative reading of it on the shipping grammar (ADR-0009): the near-miss script’s lines and 2 minutes of Polish speech get no answer. An updated microphone disclosure, shown once before the first service start whether or notRECORD_AUDIOis already granted: phase 1’s promised listening only with Home on screen. 2026-11-10: planned; built and read on the emulators, Xiaomi’s readings owed until a phone is attached - Phase F (
phase-F.md) — Fuchy (ADR-0015): Commands stored as fuchy, Darek’s own bundled;INTERNET(ADR-0008); the native fuchy page, install from it or from a file through Darek’s picker, the signature check and badge, permissions per fucha and for all, updates, „Zgłoś fuchę”. The first fucha is the weather, installed from the page, for a city the user sets once. No fucha calls a Number or reads location here: calling stays phase 3’s, location waits for the first fucha that needs it. Thedarek-fuchyrepo and the builder on the site are their own project. 2026-11-13: planned - Phase 3 — Safe for a stranger: the user’s Numbers for the built-in Commands, which brings
CALL_PHONEand the call into the release, the user’s own Reply text for each Command (ADR-0014), and the first-run path. Before a Command that calls can fire, phase 1’s R1 is finished and its R2 taken; under Daria, her name and Decoys are read withGATEin the grammar, on the shipping grammar, against real voices, to phase 1’s bar; and under either Persona the hold after the name is read against a Command in a second voice (ADR-0009,DOD.md); a fucha that calls a Number arrives here too. It freezesCALL_PHONEand themicrophoneservice type. The door is already open: closed testing started with phase 0’s Alpha 0.2.0 (2026-10-31), so the microphone disclosure moved to phase 1, whereRECORD_AUDIOarrives - Phase N — Ship shape: Polish-first listing, screenshots, the foreground-service declaration and its video, the App content answers
Rules that cut across every phase
1. Decide where the door is, early
The door is the first build a stranger installs — closed testing, for a new personal Play
account. Everything that reaches a tester’s phone is harder to change afterwards: a stored key, a
permission, a minSdk, the applicationId. So name the phase the door sits after, and write down what
it freezes. The closed-test window is calendar time and other people’s replies, so later phases can
run while it waits.
Do not plan a decision on the testers’ answers. The first app built from this template left two
phases’ open questions — defaults, whether a behaviour was too clever — to be asked of its closed-test
testers. The survey that did go out asked none of them, and both closed unanswered, on the values
that shipped. What carried
the decisions was the device readings. Decide on a reading, ship a default, and let the mail hand-off
(ui/support/, live from the door) tell you if it is wrong.
2. When the two halves of a phase cost wildly different amounts, they are two phases
The first app built from this template split every one of its middle phases along cost or along kind, because in each case a bundle of a cheap item with an expensive one — or a feature with the safety work it depends on — hid which half was blocking. However neatly one sentence describes both, two costs are two phases.
3. A phase proves itself on the phone, not in a test
Every phase ends with readings taken off a real device and written into its phase-N.md, with the
command that produced them. A green test run says the code does what the test asked; the phone says
what the ROM allowed. Vendor ROMs are where those two part company.
But the rule has a second clause, and leaving it out is how a roadmap arrives at no tests at all.
Most of the risk in an app this size is device behaviour, and none of that is reachable by a unit
test — but the pure functions that compute or bound a safety value are the exception, and each of
them fails in exactly the direction the app exists to prevent. So: device behaviour is proven by
measurement; a safety value is proven by test, and the phase that introduces one introduces its
test. For the first app built from this template that came to five tests across the whole roadmap
— a window that crosses midnight, two deadlines resolved against a clock, a ramp whose bottom end
turns the backlight off over a live touchscreen, the width of a window that swallows touches, and a
restored intent that had to be refused. Not a testing culture. Five tests standing in front of the
properties CLAUDE.md calls load-bearing.
⚠ Reconcile the list when the roadmap closes, and expect the count to be wrong. That app planned four and shipped five: the fifth arrived because a device reading found a fault no test had been written for. That is the rule working, not an exception to it — the top of a ramp is not visible on a device until it strands somebody.
4. Ask for nothing before the feature that needs it
An app of this shape ends up asking the user for three or four things, and most of them are hand-offs
to settings screens whose result cannot be read back reliably — appops has been caught lying about
SYSTEM_ALERT_WINDOW on a real phone. So the sequence is a rule rather than a phase:
Every ask waits for the moment the feature needing it is switched on. The phase that introduces an ask owns its hand-off, its re-read, and what the user sees when the answer is no.
Write the table for your app — the ask, the moment it is made, the phase that owns it — and put it here, so a later phase cannot quietly move an ask forward to “get it out of the way”.
| Ask | Made when | Phase |
|---|---|---|
RECORD_AUDIO, after the disclosure sheet |
the first tap of Słuchaj | 1 |
| The disclosure again, for the background (no permission) | the first service start, granted microphone or not | 2 |
CALL_PHONE |
a Number is first saved for a Command, bundled or from a fucha (debug-only in phase 1) | 1 (debug), 3 |
POST_NOTIFICATIONS |
Listening is first started as the service | 2 |
| Battery exemption, autostart (hand-offs, no permission) | a sheet once, after the first service start, on a ROM that needs them; then rows in Settings | 2 |
| A fucha’s permissions, Darek’s own grant per fucha or for all (ADR-0015) | the fucha is switched on, at install or, for a bundled one, by the user | F |
| Approximate location, behind that grant | the first fucha declaring it is switched on, once its own decision is made (ADR-0015) | the phase that brings that fucha, not F |
The third clause is the one that gets left out, and it is the one that generates support mail.
These denials nearly all fail silently: a denied notification permission leaves a foreground
service running with no way to stop it, a denied autostart leaves reboot restore never firing, a
denied battery exemption leaves a schedule never firing. In each case the app knows and the user does
not. A re-read tells the app; nothing tells the person holding the phone unless a screen does.
ui/common/Surfaces.kt’s WarningBanner is the idiom for a state the app can re-read; a permanent
row that makes no claim is the idiom for one it cannot.
Autostart is the case where even the re-read is out of reach. It is not an appop and there is no
API for it, so no code of yours can tell a granted state from a denied one — only
scripts/device-gate.py, over adb, run by you. The clause survives it: the phase still owns the
hand-off and still owns saying what a denial costs, without claiming to know the answer. What changes
is only who can check.
The same logic applies to side effects, not only to permissions. A feature that makes one of the phone’s own controls go inert is a system control that appears broken, caused by you. The phase that introduces the behaviour owns saying so.
Phase 0 — Skeleton
(one paragraph)
Phase P — The pipeline
(one paragraph — DOD.md’s “Before the first upload” is most of it)
Not in this plan
(what the app will deliberately never do, so a phase cannot quietly grow it back)