Files
clockula/CONTRIBUTING.md
Jean-Luc MakiolaandClaude Opus 5 7b7b730219
Release — F-Droid repo + Gitea/Codeberg release + Play / release (push) Failing after 3m42s
Release — F-Droid repo + Gitea/Codeberg release + Play / play (push) Skipped
Release — F-Droid repo + Gitea/Codeberg release + Play / detect (push) Successful in 6s
docs: project README, changelog, and M0 marked done
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
2026-09-11 11:47:23 +02:00

5.7 KiB

Contributing to Clockula

Thanks for your interest in Clockula — a Material 3 Expressive clock app: alarms, timers, stopwatch and world clock. Unlike its siblings it owns its storage, because there is no open provider behind a clock. Before diving in, skim docs/ARCHITECTURE.md (how it's built), docs/ROADMAP.md (what's next), and docs/PLAN.md (the design rationale). This file covers the practical how.

The one architectural rule

Everything above the data layer talks to a repository and sees only domain types and Flows. Room entities, DAOs and @Query strings never leak above the data layer. Clockula owns its storage, which makes this rule more important here than in the siblings, not less: it is the only thing keeping the schema a private implementation detail rather than the app's de-facto domain model. If a change would expose a Room type to a ViewModel or the UI, it's in the wrong layer — see docs/PLAN.md §0.

Prerequisites

  • JDK 17
  • Android SDK: compileSdk 37, build-tools 36.0.0 (the Gradle wrapper handles AGP/Kotlin)
  • A real device for anything touching the alarm engine. An emulator will not tell you whether an alarm survives doze, a reboot, or an OEM battery-saver — and that is the only question that matters about an alarm.

Build, test, lint

./gradlew :app:assembleDebug        # build the debug APK
./gradlew :app:testDebugUnitTest    # JVM unit tests (JUnit5 + Truth + Turbine)
./gradlew lintDebug                 # Android lint (CI runs this on every PR)

CI (.forgejo/workflows/ci.yaml, on Codeberg) runs a reproducible-release invariant check, then lint → unit tests → debug build on every pull request, so run these locally before opening a PR. Keep CI green.

Translations

Never edit a values-*/strings.xml file in a pull request. Translations are owned by a self-hosted Weblate that writes to this repository directly, and a hand-edit is overwritten on the next sync.

Translate Clockula on Weblate

Adding a new English string to values/strings.xml is normal PR work; Weblate picks it up and offers it to translators. Partial translations are expected and fine — missing keys are informational. Stale and orphaned keys are not, so run

python3 scripts/check_translations.py

before pushing. It reports those more clearly than lint's MissingTranslation does, and it's what the Translations check runs on every PR.

A new language also needs one <locale> line in app/src/main/res/xml/locales_config.xml — that file is the single source of truth for both the in-app picker and the Android 13+ per-app language setting.

Where to put code

Layer Lives in Rule of thumb
Pure logic (models, filtering, sorting, form validation, date maths) domain/ No Android imports — must be JVM-unit-testable.
Provider access data/tasks/ The only place that knows about the provider. New provider work goes through TasksDataSource.
Reminders, prefs, DI, demo data data/reminders/, data/prefs/, data/di/, data/demo/
Screens ui/<area>/ One ViewModel + immutable UiState per area; Compose for the screen.

Code style & conventions

  • Kotlin, 4-space indent, LF line endings, final newline, no trailing whitespace — all enforced by .editorconfig (2-space for yaml/toml/json/md). Match the surrounding code.
  • Material 3 Expressive for all UI: use MaterialExpressiveTheme, the colour-scheme tokens (never hardcoded colours), and canonical M3 components (e.g. ListItem for rows). Consult the material-3 skill before designing a new screen or component.
  • Prefer the domain layer for anything testable; keep AndroidTasksDataSource the only Android-coupled data implementation so the rest stays JVM-testable.

Tests

  • New domain logic (mappers, filters, sorting, forms, value mapping) must come with JVM unit tests under app/src/test/. The data source is the JVM-testable seam — mock or fake it rather than reaching for instrumentation.
  • Add an instrumented test only when a path genuinely needs a real ContentResolver.

Commits & PRs

  • Write focused commits with clear messages (the existing history uses short, scoped subjects like UI: ... / M1 ...).
  • Update CHANGELOG.md under [Unreleased] for any user-visible change — its sections feed the release notes and F-Droid "What's New" (see docs/RELEASING.md).
  • If your change shifts the architecture or completes a milestone, update docs/ARCHITECTURE.md / docs/ROADMAP.md in the same PR.
  • Don't bump versionName / versionCode in a regular PR — the committed versionName is bumped only when cutting a release (that bump reaching main is what triggers the release; the pipeline then mints the tag). See docs/RELEASING.md.

Scope

v1 is four surfaces — alarms, timers, stopwatch, world clock — done properly, and nothing else. Widgets, a Quick Settings tile, screensaver mode and a bedtime schedule are all wanted, and all deliberately deferred until v1 is finished; see docs/ROADMAP.md.

Two things are out of scope by design. A sync stack of our own — your alarms export to a documented JSON file, and syncing them is a job for something that already does syncing well. Anything that makes an alarm less reliable in exchange for a feature — if a change trades away the certainty that an alarm rings, it loses, however nice the feature is.

License

By contributing you agree your contributions are licensed under the project's MIT License.