# Contributing to Calendula

Calendula is a Material 3 Expressive calendar app that lives strictly on top of
Android's `CalendarContract` — no app database, no sync stack, no network access.
That constraint shapes most review comments, so
[`docs/ARCHITECTURE.md`](docs/ARCHITECTURE.md) is worth skimming before you write
code. This file is the practical how.

**[Codeberg](https://codeberg.org/jlmakiola/calendula) is the canonical home** —
issues, pull requests, releases. The self-hosted Gitea instance referenced in the
release docs is build infrastructure only; there is nothing to contribute there.
Be decent to the people you meet in the tracker.

## Start with an issue

| You want to | Do this |
|---|---|
| Add a feature | **Open an issue first** and wait for a go-ahead |
| Fix a bug | Open an issue, then a pull request |
| Fix a typo, a comment, or docs | Just open the pull request |
| Add or fix a translation | **Don't** — [use Weblate](#translations) |

Features get an opinion before they get code: whether Calendula should do a
thing at all is the one decision a patch can't make. A feature PR that arrives
without a discussed issue may be closed unmerged even when the code is good —
please don't spend a weekend on one first.

Bugs are more straightforward, but still start with an issue: it's what carries
the milestone and gives the changelog something to link.

Issue templates cover bug, crash, feature and question. For a crash, let the app
do the work — **Settings → Report a problem**, or the prompt shown after a crash,
captures the stack trace and prefills the form. The report contains app, Android
and device versions plus the trace; no calendar content, no personal data.

## Which branch to target

Calendula releases by merging a version bump into `main`, so `main` is a release
trigger rather than a staging area. Work is assembled on release branches first.

Once your issue has a milestone, that milestone names your branch:

| Milestone | Target branch |
|---|---|
| `2.18.0` | `release/v2.18.0` |

Every milestone has a matching branch. If it's somehow missing, target `main` and
mention it in the PR — it will be retargeted. Don't pick an older release branch:
they're kept after shipping, so the newest one isn't necessarily yours.

## Translations

**Never edit a `values-*/strings.xml` file in a pull request** — German included.
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 Calendula on Weblate](https://weblate.dev.jeanlucmakiola.de/engage/calendula/)**

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.

## Build & test

```sh
git clone --recurse-submodules https://codeberg.org/jlmakiola/calendula.git
```

The `floret-kit` submodule is a composite build compiled from source. An
existing clone needs `git submodule update --init --recursive`, or nothing
resolves.

- **JDK 17** — not newer; the Android Gradle Plugin requires exactly 17. Set
  `JAVA_HOME` if your default differs.
- **Android SDK** — platform 37 (`compileSdk`) and build-tools 36.0.0, located
  via `ANDROID_HOME` or a gitignored `local.properties` with `sdk.dir`. If you
  go the `local.properties` route the included build needs its own copy at
  `floret-kit/local.properties`; `ANDROID_HOME` covers both at once and is the
  easier path.

The Gradle wrapper is checked in, so no system Gradle is needed.

```sh
./gradlew lint test assembleDebug     # roughly what CI runs
```

A single test class, or a pattern:

```sh
./gradlew testDebugUnitTest --tests "de.jeanlucmakiola.calendula.domain.SimpleRecurrenceTest"
./gradlew testDebugUnitTest --tests "*SimpleRecurrence*"
```

CI reports one `CI` check per pull request: `lintDebug`, `testDebugUnitTest`,
`assembleDebug`, and a Trivy scan. Pull requests touching only docs, F-Droid
metadata or the licence skip the Android build and go green quickly. More detail
in [`docs/BUILDING.md`](docs/BUILDING.md).

## The rules

These are the ones that turn into review comments.

1. **No network.** Calendula holds no `INTERNET` permission, and that's a
   feature rather than an oversight. Anything that would need one is a product
   decision before it's a patch — the crash reporter deliberately opens a
   prefilled web issue instead of posting anything itself.
2. **The provider is the only database.** No Room, no cache, no local mirror of
   events. `CalendarContract` is the single source of truth, which is also why
   externally synced changes work for free.
3. **Don't patch UI state after a write.** A `ContentObserver` re-queries and
   views recompose from fresh provider state. Hand-patching a list after saving
   appears to work, then quietly diverges from what the provider actually stored.
4. **`domain/` has no Android imports.** Models, validation, recurrence
   rendering, conflict snapshots and the `.ics` codec stay pure Kotlin so they
   remain JVM-testable.
5. **Tests run on the JVM.** JUnit 5 + Truth + Turbine. The seams exist for you:
   fake the data source (`FakeCalendarDataSource`), and feed mappers plain maps
   through `ColumnReader` instead of cursors. Instrumented tests are a last
   resort, not a default.
6. **Read before touching the subtle pipelines.** Recurring writes (UNTIL vs
   DURATION, exception URIs, series splits), save-conflict detection and reminder
   delivery (post-before-mark) follow provider-driven rules that are documented
   in [`docs/ARCHITECTURE.md`](docs/ARCHITECTURE.md) and are not guessable from
   the code alone.
7. **Don't break reproducible builds.** `vcsInfo`, `dependenciesInfo` and the AGP
   metadata block are disabled on purpose so the official F-Droid repo can verify
   our binary against a from-source rebuild.
   `scripts/check_reproducible_release.sh` runs on every pull request, including
   docs-only ones.

## UI conventions

Material 3 Expressive throughout, built from the system's own tokens and
components — colour-scheme tokens rather than hardcoded colours, `ListItem` for
settings rows.

**Selection pickers are full-screen.** Every browse-style "choose one" surface
uses floret-kit's `FullScreenPicker` / `OptionPicker`; one that needs a commit or
extra action passes it through the picker's `actions` slot. The exception is the
recurring-scope chooser (*this / this and following / all*), which stays a
compact dialog — a two- or three-option decision reads better as a popup than as
a nearly empty screen. `AlertDialog` is for plain confirmations only, and radio-
or text-list dialogs aren't used at all.

Shared UI machinery lives in the `floret-kit` submodule and has
[its own contributing guide](https://codeberg.org/jlmakiola/floret-kit/src/branch/main/CONTRIBUTING.md);
changing it means a pull request against that repository plus a submodule bump
here.

## Commits & pull requests

Conventional commits, scoped to the area you touched:

```
fix(calendars): keep an event's own calendar when it is switched off
feat(month): pull-to-expand the split view (#38)
docs(architecture): record what the second review pass changed
```

Types in use: `feat` `fix` `docs` `refactor` `style` `chore` `ci` `build`
`revert`. Reference the issue in the subject or the body. Keep commits small —
small commits revert cleanly, which matters more here than a tidy history.

If your change is user-visible, add an entry under `## [Unreleased]` in
[`CHANGELOG.md`](CHANGELOG.md). Match the surrounding voice: entries describe
what changed *for the person using the app*, and why, not what changed in the
code. Link the issue and add its reference at the bottom of the file. It may get
reworded when the release is cut, so don't agonise over it.

Please don't commit planning or design documents. Code, tests, architecture notes
and the changelog land; the reasoning belongs in the commit message and the
issue.

## Licence

Calendula is [MIT](LICENSE). By contributing you agree your changes ship under
the same licence.
