The README leads with the thing that is actually different about this app rather than burying it: Calendula and Agendula front standards someone else maintains, and Clockula cannot, because there is no open provider behind a clock. Saying so plainly is better than implying a thesis the app does not meet — and the three things that replace it (the AlarmClock contract, IANA zones, an exportable format) are real commitments, not consolation. CONTRIBUTING's scope section now names the trade this project will not make: nothing ships that buys a feature by making an alarm less certain to ring. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L94fydiJC37LtxVusNQBDy
175 lines
8.3 KiB
Markdown
175 lines
8.3 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)
|
|
|
|
✅ **M0 done.** The project builds, installs and shows a themed placeholder;
|
|
floret-kit is wired in as a composite build, the identity is seeded, and the
|
|
full Codeberg-canonical release pipeline is in place. **M1** (the `core-prefs` +
|
|
`core-di` extraction into floret-kit) 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.
|
|
|
|
✅ `assembleDebug`, `lintDebug` and `testDebugUnitTest` all pass;
|
|
`scripts/check_reproducible_release.sh` reports every invariant holding.
|
|
Two notes for later: the store listing and `fastlane` changelogs are written
|
|
but no screenshots exist yet (M11), and `docs/ARCHITECTURE.md` is still
|
|
unwritten by design — M2 writes it, once there is an architecture to
|
|
describe.
|
|
|
|
### ⬜ 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.
|