Makes Codeberg canonical for git, issues, PRs, tags and releases. The self-hosted Gitea instance stays build infrastructure: signing key, F-Droid publishing, release pipeline. Ports the setup Calendula already runs on, adapted where Agendula genuinely differs. **This PR is its own test.** It is the first PR opened on Codeberg, so a green `CI` check proves the new runner works *and* that the submodule resolves from its new home. ### 1 · Workflows split by directory Forgejo's lookup is first-match-wins across `.forgejo/` → `.gitea/` → `.github/`, and Gitea cannot see `.forgejo/` at all. So each forge sees exactly one set, with no duplicated files and no expression to keep in sync: | Directory | Runs on | Contains | Secrets | | --- | --- | --- | --- | | `.forgejo/workflows/` | Codeberg | `ci.yaml`, `translations.yaml` | **none** | | `.gitea/workflows/` | Gitea | `release.yaml`, `renovate.yml` | all of them | The line is drawn at **secrets, not CI-vs-release** — that is what makes fork PRs safe. Renovate deliberately does *not* move despite opening PRs here; it keeps running where its token already lives and merely talks to Codeberg's API. CI also gains three fixes: an explicit `SKIP_RE` skip-list that names the build-relevant files in the log, base-ref normalisation, and a fully-qualified `android-actions/setup-android` — Codeberg resolves bare `uses:` refs against `data.forgejo.org`, which does not carry that action. ### 2 · Three release-pipeline safety changes - `detect` and the Renovate job get an explicit `repository_owner` guard. The directory split only holds while `.forgejo/` is non-empty; empty it and Codeberg would fall back to `.gitea/` and start running these on the contributor-facing runner, without secrets. - `detect` now reads tags from **Codeberg**, not from the Gitea instance it runs on. Push mirroring is `git push --mirror`, so a tag minted on Gitea is deleted by the next sync until the Codeberg tag push propagates back — asking Gitea inside that window reports "no tag" for an already-shipped release and would cut it twice. It also now fails on any status other than 200/404 rather than reading a transient error as "no tag": a failed job is recoverable, a duplicate release is not. - **The Codeberg publish step pushes the tag itself** instead of waiting for it to arrive by mirror. That wait was correct while Gitea mirrored *to* Codeberg; under Codeberg-canonical the mirror runs the other way and it would never resolve. Attaching the release to an already-pushed ref (no `target_commitish`) is what avoids the empty-bodied 500s, and the create call retries with backoff because Codeberg 500s on a tag it has only just received. The step stays **fail-loud**, not `continue-on-error` — it reported green through 0.2.1–0.3.2 while never once publishing, and that must not be possible again. ### 3 · Renovate `renovate.json5` plus a Gitea-side job targeting Codeberg's API. `managerFilePatterns` covers **both** workflow directories, so the pinned Renovate image tag and the action versions in either file keep getting bumped. Needs two new Gitea secrets: `RENOVATE_TOKEN` (Codeberg bot, repo read/write + PR scope) and `GITHUB_COM_TOKEN` (read-only github.com PAT, for changelog lookups). ### 4 · Weblate A parity check (`scripts/check_translations.py`) runs on every PR without a path filter, so the required `Translations` status is always reported. Partial translations are expected, so `MissingTranslation` and `MissingQuantity` become informational — `ExtraTranslation` stays fatal. Agendula had no `lint` block at all, so the first locale to land would otherwise have failed the build. **Settings → App language** now opens a picker carrying a "Help translate" header. That is why it drops floret-kit's `LanguagePickerRow` for a local row: the shared recipe has no `header` slot, and the framing is app-specific rather than a family primitive. ### 5 · Links repointed In-app Source / License / report-issue URLs, F-Droid metadata, README (now with a Codeberg CI badge), and the docs. `floret-kit` follows suit — `.gitmodules` points at `codeberg.org/jlmakiola/floret-kit`, so a clone no longer needs to reach the personal Gitea instance to resolve it. The Gitea copy is **kept**: every existing tag records the old submodule URL, so rebuilds of past releases still resolve. ### 6 · Housekeeping Drops `release-notes.md` — a release-pipeline scratch file that got committed — and gitignores the five others the release job writes into the workspace. ### Not in this PR The Codeberg → Gitea push mirror, the Weblate component, and the Codeberg bot account (all browser-side). Until the mirror is flipped, merging this does **not** reach the Gitea runner. Co-authored-by: Jean-Luc Makiola <business@jeanlucmakiola.de> Reviewed-on: https://codeberg.org/jlmakiola/agendula/pulls/2
119 lines
5.4 KiB
Markdown
119 lines
5.4 KiB
Markdown
# Contributing to Agendula
|
|
|
|
Thanks for your interest in Agendula — a Material 3 Expressive task app that's a
|
|
pure front-end over the OpenTasks `TaskContract` provider, with no own database
|
|
or sync stack. Before diving in, 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 `TasksRepository` and sees only domain
|
|
types and Flows. **Provider column names, `TaskContract`, `ContentResolver`, and
|
|
the authority string never leak above `data/tasks/`.** This is what keeps
|
|
"Posture B" (bundling the provider later) an additive change instead of a
|
|
rewrite — see [`docs/ARCHITECTURE.md`](docs/ARCHITECTURE.md) §7. If a change
|
|
would expose provider details to a ViewModel or the UI, it's in the wrong layer.
|
|
|
|
## Prerequisites
|
|
|
|
- JDK 17
|
|
- Android SDK: compileSdk 37, build-tools 36.0.0 (the Gradle wrapper handles AGP/Kotlin)
|
|
- A device or emulator with **OpenTasks** or **tasks.org** installed for
|
|
anything touching the read/write paths (ideally with DAVx5 syncing a CalDAV
|
|
task list, so there's real data). Debug builds fall back to `DemoSeeder` for
|
|
sample data.
|
|
|
|
## 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 Agendula on Weblate](https://weblate.dev.jeanlucmakiola.de/engage/agendula/)**
|
|
|
|
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, 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`](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
|
|
|
|
Agendula stays true to its thesis: a front-end over **open** task backends
|
|
(CalDAV / iCalendar / DecSync via the OpenTasks provider). Proprietary backends
|
|
(Google Tasks, Microsoft To Do) are out of scope by design — they'd mean owning
|
|
a sync stack. v1 targets the OpenTasks contract (OpenTasks + tasks.org); jtx's
|
|
richer contract is a possible later addition.
|
|
|
|
## License
|
|
|
|
By contributing you agree your contributions are licensed under the project's
|
|
[MIT License](LICENSE).
|