Third floret after Calendula and Agendula: a Material 3 Expressive clock app (alarms, timers, stopwatch, world clock). The family thesis doesn't survive contact with a clock — there is no open provider behind one — so PLAN opens by saying so plainly and relocates the open-standards commitment to the three places it can actually live: the full android.provider.AlarmClock intent contract, IANA tzdata, and a documented JSON backup format. Locked: four surfaces and no extras in v1, Room + DataStore + JSON export, maximum alarm reliability including a self-check diagnostics screen, seed #6B7A5C (completing the family's Calendula→Agendula colour rotation), four tabs plus a live running-state pill, Codeberg-canonical from commit one, and floret-kit from day one with core-prefs + core-di extracted up front. ROADMAP carries M0–M11 as an ordered work queue with per-milestone done criteria, written to be executed one milestone per loop iteration. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L94fydiJC37LtxVusNQBDy
165 lines
7.8 KiB
Markdown
165 lines
7.8 KiB
Markdown
# Clockula — roadmap
|
|
|
|
The **status** view, and the work queue. Design rationale for every decision here
|
|
lives in [`PLAN.md`](PLAN.md); the shape of the code as it stands will live in
|
|
[`ARCHITECTURE.md`](ARCHITECTURE.md) from M2 onward.
|
|
|
|
Status legend: ✅ done · 🚧 in progress · ⬜ not started
|
|
|
|
---
|
|
|
|
## Current state (one line)
|
|
|
|
⬜ **Nothing built yet.** The spec is locked (`PLAN.md`); M0 is next.
|
|
|
|
---
|
|
|
|
## How the loop works through this
|
|
|
|
One milestone per iteration, auto-continuing. Each iteration:
|
|
|
|
1. Pick the **first milestone not marked ✅**.
|
|
2. Implement it **completely** — every checklist item under it.
|
|
3. Build, and run the tests the milestone names. A milestone is not done until
|
|
they pass.
|
|
4. **Commit** (floret-kit submodule first where it changed, then Clockula's
|
|
pointer bump — separate commits, granular, lowercase area prefix).
|
|
5. Mark the milestone ✅ here, update **Current state**, and commit that too.
|
|
6. Continue to the next milestone.
|
|
|
|
Rules that hold across every iteration:
|
|
|
|
- **Never let a Room entity or `@Query` string leak above the data layer**
|
|
(`PLAN.md` §0).
|
|
- **Never** add a foojay toolchain resolver, anywhere, not even in a comment.
|
|
- Commit each finished unit of work; never accumulate a WIP pile.
|
|
- If a milestone turns out to be wrong, say so and amend `PLAN.md` — do not
|
|
silently build something else.
|
|
- `ARCHITECTURE.md` and `CHANGELOG.md` are updated as part of the milestone that
|
|
changes them, not in a later cleanup pass.
|
|
|
|
---
|
|
|
|
## v1 milestones
|
|
|
|
### ⬜ M0 — Skeleton & pipeline
|
|
The project exists, builds, is themed, and ships itself.
|
|
- Gradle project: `settings.gradle.kts`, version catalog copied from Agendula
|
|
(drop `:provider`, add Room + KSP), AGP 9.x conventions, `vcsInfo` off on
|
|
release, no foojay resolver.
|
|
- floret-kit as a git submodule + `includeBuild`, with the gitignored
|
|
`floret-kit/local.properties` arrangement documented.
|
|
- Hilt, Compose, `MainActivity`, `FloretExpressiveTheme` reseeded to `#6B7A5C`
|
|
(`ui/theme/Color.kt` + `Theme.kt`), hand-tuned light/dark schemes.
|
|
- `core-crash` wired; `core-locale` + `res/xml/locales_config.xml`.
|
|
- Codeberg repo, canonical from the first commit. Full pipeline per `PLAN.md` §10:
|
|
`ci.yaml`, `translations.yaml`, `release.yaml` (Agendula spine + Calendula `play`
|
|
job), `renovate.yml`, issue/PR templates, F-Droid metadata, fastlane tree.
|
|
- `README.md`, `LICENSE` (MIT), `CHANGELOG.md`, `.editorconfig`, `.gitattributes`,
|
|
`.gitignore`, `docs/README.md` index.
|
|
- **Done when:** it builds, installs, shows a themed placeholder, and CI is green.
|
|
|
|
### ⬜ M1 — floret-kit: `core-prefs` + `core-di`
|
|
Pay the extraction the kit's roadmap has been waiting for a third app to trigger.
|
|
- In the kit: `core-prefs` (`ThemeMode`, `dynamicColor`, typed DataStore wrapper,
|
|
`toEnum()`) and `core-di` (`@IoDispatcher` + provider), each with tests, module
|
|
docs, `CHANGELOG` and `ROADMAP`/`ARCHITECTURE` updates.
|
|
- Clockula consumes both. Migrating Calendula and Agendula is explicitly **out of
|
|
scope** — note it in the kit's roadmap as a follow-up.
|
|
- **Done when:** the kit's tests pass, Clockula builds against the new modules,
|
|
and both repos are committed (submodule first, then the pointer bump).
|
|
|
|
### ⬜ M2 — Data layer
|
|
The whole storage stack, headless. No UI.
|
|
- Room: `alarms`, `timers`, `world_clocks`, `stopwatch_laps` (`PLAN.md` §5),
|
|
schema exported and committed, DAOs.
|
|
- Domain models (plain Kotlin) + mappers. Repositories exposing Flows.
|
|
- DataStore prefs (theme, dynamic colour, defaults, stopwatch running state).
|
|
- DI modules.
|
|
- **Tests:** mappers, repository behaviour, the elapsed-realtime vs wall-clock
|
|
distinction under a simulated clock change.
|
|
- Write the first `ARCHITECTURE.md`.
|
|
|
|
### ⬜ M3 — Alarm engine
|
|
The hard part (`PLAN.md` §4), still headless apart from the ring screen's
|
|
plumbing.
|
|
- Next-fire resolution: local time-of-day + repeat mask → next instant in the
|
|
device zone; `skipNextOccurrence`; snooze.
|
|
- `AlarmScheduler` over `setAlarmClock`, single-next-alarm registration,
|
|
`USE_EXACT_ALARM` with a `SCHEDULE_EXACT_ALARM` fallback path.
|
|
- Receivers: fire, `BOOT_COMPLETED`, `TIME_SET`, `TIMEZONE_CHANGED`,
|
|
`MY_PACKAGE_REPLACED`.
|
|
- Ringing foreground service: audio focus, `USAGE_ALARM`, volume ramp, vibration,
|
|
ringtone URIs; survives process death and reboot mid-ring.
|
|
- Full-screen-intent notification + `canUseFullScreenIntent()` handling, degrading
|
|
to a high-priority heads-up notification that still rings — never to silence.
|
|
- **Tests:** DST spring-forward and fall-back, every repeat configuration, skip,
|
|
snooze-vs-next-occurrence. These are the tests the app lives or dies on.
|
|
|
|
### ⬜ M4 — App shell
|
|
- Navigation host, four tabs, M3 navigation bar, adaptive rail on wide layouts.
|
|
- The **live pill** (`PLAN.md` §9) — shared running-state surface, pause + stop
|
|
from any tab.
|
|
- The ring screen UI: over-lockscreen, turn-screen-on, snooze/dismiss, optional
|
|
dismiss challenge that cannot strand the user.
|
|
- Motion from the kit's `identity` module; predictive back.
|
|
|
|
### ⬜ M5 — Alarms screen
|
|
List, create, edit, delete, enable/disable. Time picker, repeat-day selector,
|
|
label, ringtone picker, vibrate, snooze settings, per-alarm overrides. Next-fire
|
|
line ("in 9h 12m") on each row. Built on `GroupedSurface`/`GroupedRow`.
|
|
|
|
### ⬜ M6 — Timers
|
|
Multiple concurrent timers, presets, labels, add/pause/reset/+1min. Foreground
|
|
service with notification controls, expiry ringing reusing M3's audio path.
|
|
Elapsed-realtime anchored.
|
|
|
|
### ⬜ M7 — Stopwatch
|
|
Start/stop/reset, laps with splits and cumulative times, best/worst lap emphasis.
|
|
Foreground service so it survives backgrounding; laps persist across process
|
|
death. This is where the big-readout typography gets settled for the whole app.
|
|
|
|
### ⬜ M8 — World clock
|
|
IANA zone list with search, ICU-localised city and zone display names, offset and
|
|
day-difference relative to home, reorder, home-zone handling. The analog face
|
|
built from `MaterialShapes` / `androidx.graphics.shapes` — the app's one
|
|
deliberate showpiece.
|
|
|
|
### ⬜ M9 — System interop
|
|
The full `android.provider.AlarmClock` contract (`PLAN.md` §6): `SET_ALARM`,
|
|
`SET_TIMER`, `SHOW_ALARMS`, `SHOW_TIMERS`, `DISMISS_ALARM`, `SNOOZE_ALARM`, plus
|
|
next-alarm publishing. Hostile-input validation at the intent boundary.
|
|
- **Tests:** extra validation, including malformed and out-of-range input.
|
|
|
|
### ⬜ M10 — Settings, backup, and the self-check
|
|
- Settings composed from kit components: theme, dynamic colour, language,
|
|
alarm/timer/clock defaults, about + crash reporting.
|
|
- JSON backup export/import via SAF (`PLAN.md` §7), schema documented in the repo.
|
|
- The **"why might my alarm not ring?"** self-check screen (`PLAN.md` §4) —
|
|
real device state, deep links to the exact settings pages.
|
|
- **Tests:** backup round-trip and unknown-field tolerance.
|
|
|
|
### ⬜ M11 — Release readiness
|
|
Translations wired to Weblate, fastlane metadata and screenshots, F-Droid
|
|
reproducibility guard verified against the submodule, instrumentation smoke
|
|
tests, accessibility pass (TalkBack on the ring screen especially), on-device
|
|
reliability soak across a few real nights. Then 1.0.0.
|
|
|
|
---
|
|
|
|
## Post-v1
|
|
|
|
Deferred deliberately, in rough priority order:
|
|
|
|
- **Dock / screensaver mode** — a `DreamService` full-screen clock. Cheapest big
|
|
win, and the surface people look at most.
|
|
- **Glance home-screen widget** — next alarm and running timer.
|
|
- **Quick Settings tile** — timer control and next alarm from the shade.
|
|
- **Bedtime / sleep schedule** — wind-down and wake, with DND handoff. A feature
|
|
in its own right, realistically v2.
|
|
- **`core-notification` in floret-kit** — revisit once Clockula's real
|
|
notification surface is known (`PLAN.md` §3).
|
|
- **Migrate Calendula and Agendula onto `core-prefs` + `core-di`** — tracked in
|
|
the kit's roadmap, not here.
|
|
- **Wear OS companion** — unscoped.
|