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

127 lines
6.1 KiB
Markdown

# 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`](docs/ARCHITECTURE.md)
(how it's built), [`docs/ROADMAP.md`](docs/ROADMAP.md) (what's next), and
[`docs/PLAN.md`](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`](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
```sh
./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](https://weblate.dev.jeanlucmakiola.de/engage/clockula/)**
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
```sh
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`](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`](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`](docs/RELEASING.md)).
- If your change shifts the architecture or completes a milestone, update
[`docs/ARCHITECTURE.md`](docs/ARCHITECTURE.md) / [`docs/ROADMAP.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`](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`](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](LICENSE).