Files
clockula/CONTRIBUTING.md
T
makiolaj e870918a03 docs: plain-language pass over every doc
Removes AI-writing patterns across the docs, store listing and
templates: dash-heavy rhythm, not-X-but-Y contrasts, one-line kickers
and decorative bold. Facts, code, links and headings are unchanged.

Also fixes leftovers copied from the sibling apps: the crash report
template promised "no calendar content", and CONTRIBUTING described
Agendula's task-provider layout, test seams and commit style.
2026-10-06 19:14:11 +02:00

6.1 KiB

Contributing to Clockula

Thanks for your interest in Clockula, a Material 3 Expressive clock app for alarms, timers, stopwatch and world clock. Unlike its siblings it owns its storage, because there is no open provider behind a clock. Before you start, 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, scheduling and ring policies, formatting, the self-check rules) domain/ No Android imports; must be JVM-unit-testable.
Storage data/ (db/, alarms/, timers/, stopwatch/, worldclocks/, prefs/, backup/) The only place that knows Room or DataStore exists. Everything above it goes through a repository interface.
Engines and their Android seams alarm/, timer/, stopwatch/, ring/, interop/, widget/ The engine is plain Kotlin; AlarmManager, services and receivers sit behind seams in its android/, receiver/ and service/ subpackages.
Screens ui/<area>/ One ViewModel + immutable UiState per area; Compose for the screen.

docs/ARCHITECTURE.md §3 lists every package.

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 Android APIs behind the seams in each engine's android/ subpackage so the rest stays JVM-testable.

Tests

  • New domain logic (scheduling, ring policies, formatting, mappers) must come with JVM unit tests under app/src/test/. Repositories and engine seams are the JVM-testable boundary: fake them rather than reaching for instrumentation.
  • Add an instrumented test only when a path genuinely needs a real device (Room migrations, AlarmManager, a foreground service).

Commits & PRs

  • Write focused commits with clear messages (the existing history uses short, conventional subjects like feat(widgets): ... / fix(ringtones): ...).
  • 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.