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
7.8 KiB
Clockula — roadmap
The status view, and the work queue. Design rationale for every decision here
lives in PLAN.md; the shape of the code as it stands will live in
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:
- Pick the first milestone not marked ✅.
- Implement it completely — every checklist item under it.
- Build, and run the tests the milestone names. A milestone is not done until they pass.
- Commit (floret-kit submodule first where it changed, then Clockula's pointer bump — separate commits, granular, lowercase area prefix).
- Mark the milestone ✅ here, update Current state, and commit that too.
- Continue to the next milestone.
Rules that hold across every iteration:
- Never let a Room entity or
@Querystring 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.mdandCHANGELOG.mdare 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,vcsInfooff on release, no foojay resolver. - floret-kit as a git submodule +
includeBuild, with the gitignoredfloret-kit/local.propertiesarrangement documented. - Hilt, Compose,
MainActivity,FloretExpressiveThemereseeded to#6B7A5C(ui/theme/Color.kt+Theme.kt), hand-tuned light/dark schemes. core-crashwired;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 + Calendulaplayjob),renovate.yml, issue/PR templates, F-Droid metadata, fastlane tree. README.md,LICENSE(MIT),CHANGELOG.md,.editorconfig,.gitattributes,.gitignore,docs/README.mdindex.- 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()) andcore-di(@IoDispatcher+ provider), each with tests, module docs,CHANGELOGandROADMAP/ARCHITECTUREupdates. - 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. AlarmScheduleroversetAlarmClock, single-next-alarm registration,USE_EXACT_ALARMwith aSCHEDULE_EXACT_ALARMfallback 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
identitymodule; 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
DreamServicefull-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-notificationin 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.