diff --git a/.gitea/workflows/ci.yaml b/.forgejo/workflows/ci.yaml similarity index 92% rename from .gitea/workflows/ci.yaml rename to .forgejo/workflows/ci.yaml index 9f15571..65831ab 100644 --- a/.gitea/workflows/ci.yaml +++ b/.forgejo/workflows/ci.yaml @@ -72,9 +72,14 @@ jobs: distribution: 'zulu' java-version: '17' + # Fully qualified on purpose. Codeberg resolves bare `uses:` refs against + # data.forgejo.org, Forgejo's own action mirror — actions/checkout, + # setup-java and cache all exist there, but android-actions/setup-android + # does not, and the job dies with "repository not found". Gitea's instance + # defaults to GitHub, which is why this never surfaced before the split. - name: Setup Android SDK if: steps.scope.outputs.code == 'true' - uses: android-actions/setup-android@v3 + uses: https://github.com/android-actions/setup-android@v3 with: # Default ("tools platform-tools") drags in the Android Emulator # (~300 MB) which the build never uses. diff --git a/.gitea/workflows/translations.yaml b/.forgejo/workflows/translations.yaml similarity index 100% rename from .gitea/workflows/translations.yaml rename to .forgejo/workflows/translations.yaml diff --git a/.gitea/workflows/release.yaml b/.gitea/workflows/release.yaml index bf0f83e..17a4efd 100644 --- a/.gitea/workflows/release.yaml +++ b/.gitea/workflows/release.yaml @@ -27,6 +27,14 @@ jobs: # whether this push actually cuts a new release (no tag for it yet). Keeps the # heavy job from running on every merge to main. detect: + # Gitea only. The workflow directory split already keeps this file invisible + # to Codeberg — Forgejo's lookup is first-match-wins, and .forgejo/workflows + # exists — but that only holds while .forgejo/ is non-empty. Move the last + # file out of it and Codeberg would fall back to .gitea/workflows and start + # running the release pipeline on the contributor-facing runner, with no + # secrets. repository_owner differs between the two forges regardless of + # URL, proxy or instance rename, so this closes it permanently. + if: github.repository_owner == 'makiolaj' runs-on: docker outputs: is_release: ${{ steps.v.outputs.is_release }} @@ -41,8 +49,16 @@ jobs: - name: Resolve version and whether it is a new release id: v env: - TOKEN: ${{ secrets.GITHUB_TOKEN }} - API: ${{ github.server_url }}/api/v1/repos/${{ github.repository }} + # Tags are read from Codeberg, which is canonical — deliberately NOT + # from the Gitea API this workflow runs on. The Codeberg -> Gitea sync + # is a push mirror, i.e. `git push --mirror`, which deletes refs the + # source does not have. A tag minted here on Gitea is therefore wiped + # by the next sync (Codeberg does not have it yet) and only reappears + # once the tag push at the end of this workflow propagates back. + # Asking Gitea inside that window would report "no tag" for a release + # that already shipped, and cut it a second time. + # Public repo, so this read needs no token. + TAG_API: https://codeberg.org/api/v1/repos/jlmakiola/calendula run: | set -e VERSION=$(grep -oP 'versionName\s*=\s*"\K[^"]+' app/build.gradle.kts) @@ -60,15 +76,28 @@ jobs: fi # A tag for this version already existing means the release shipped on # an earlier push; do nothing. Absent => this merge cuts the release. - STATUS=$(curl -s -o /dev/null -w '%{http_code}' \ - -H "Authorization: token $TOKEN" "$API/git/refs/tags/v$VERSION") - if [ "$STATUS" = "200" ]; then - echo "Tag v$VERSION already exists — nothing to release." - echo "is_release=false" >> "$GITHUB_OUTPUT" - else - echo "No tag for v$VERSION yet — cutting the release." - echo "is_release=true" >> "$GITHUB_OUTPUT" - fi + # + # Anything other than a clean 200/404 is treated as fatal rather than + # as "no tag". A Codeberg outage or a network blip would otherwise + # read as absent and re-cut a release that has already shipped — + # republishing to F-Droid and Play. Failing here is recoverable; a + # duplicate release is not. + STATUS=$(curl -s -o /dev/null -w '%{http_code}' "$TAG_API/git/refs/tags/v$VERSION" || echo 000) + case "$STATUS" in + 200) + echo "Tag v$VERSION already exists on Codeberg — nothing to release." + echo "is_release=false" >> "$GITHUB_OUTPUT" + ;; + 404) + echo "No tag for v$VERSION on Codeberg yet — cutting the release." + echo "is_release=true" >> "$GITHUB_OUTPUT" + ;; + *) + echo "Codeberg tag lookup for v$VERSION returned HTTP $STATUS." >&2 + echo "Refusing to guess: treating this as 'no tag' could re-cut a shipped release." >&2 + exit 1 + ;; + esac # Releases: build + sign + publish, then mint the tag and Gitea release. # Also runs on manual dispatch, where it skips the build and just re-signs and diff --git a/.gitea/workflows/renovate.yml b/.gitea/workflows/renovate.yml index 5b5ebda..53f7a53 100644 --- a/.gitea/workflows/renovate.yml +++ b/.gitea/workflows/renovate.yml @@ -29,14 +29,30 @@ jobs: - name: Run Renovate run: renovate env: - # Self-hosted Gitea, not github.com. - RENOVATE_PLATFORM: gitea - RENOVATE_ENDPOINT: https://gitea.jeanlucmakiola.de/api/v1 - # Bot-account token (Gitea secret). Needs repo read/write + PR scope. + # Renovate targets Codeberg (canonical) while still RUNNING on the + # Gitea runner. Moving the job to Codeberg would put a repo-write + # token on the contributor-facing runner, which is exactly what the + # .forgejo/ vs .gitea/ split exists to prevent — so the token stays + # where the other secrets live and only the API calls cross over. + # + # Platform is `forgejo`, not `gitea`: Codeberg runs Forgejo, and the + # pinned image ships a distinct forgejo platform module. + RENOVATE_PLATFORM: forgejo + RENOVATE_ENDPOINT: https://codeberg.org/api/v1 + # Codeberg bot-account token (Gitea secret). Needs repo read/write + + # PR scope on jlmakiola/calendula. RENOVATE_TOKEN: ${{ secrets.RENOVATE_TOKEN }} # Scope to this repo only — no org-wide autodiscovery. RENOVATE_AUTODISCOVER: 'false' - RENOVATE_REPOSITORIES: '["makiolaj/calendula"]' - # Commits/PRs authored as the bot, not a real maintainer. + RENOVATE_REPOSITORIES: '["jlmakiola/calendula"]' + # Commits/PRs authored as the bot, not a real maintainer. This address + # must be a verified email on the Codeberg bot account, otherwise the + # commits show up unattributed there. RENOVATE_GIT_AUTHOR: 'Renovate Bot ' + # Read-only github.com PAT (no scopes needed). Unaffected by the forge + # move — nearly every dependency is *released* on GitHub, and without + # this, + # changelog/release-note lookups hit the 60/h anonymous rate limit + # and PRs arrive with an empty "Release Notes" section. + RENOVATE_GITHUB_COM_TOKEN: ${{ secrets.GITHUB_COM_TOKEN }} LOG_LEVEL: info diff --git a/.gitmodules b/.gitmodules index 103c066..b908ca7 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,3 @@ [submodule "floret-kit"] path = floret-kit - url = https://gitea.jeanlucmakiola.de/makiolaj/floret-kit.git + url = https://codeberg.org/jlmakiola/floret-kit.git diff --git a/CHANGELOG.md b/CHANGELOG.md index 17978a0..73cad12 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,6 +29,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 with a **"Missing a calendar?"** row that opens Settings → Calendars, where those marks then explain why a calendar isn't offered ([#76]). +### Changed +- Calendula's source code now lives on **Codeberg**, where its issues already + were. The **Source code** and **License** links in Settings → About point + there, so reporting a bug and reading the code no longer land on two different + sites. Nothing about the app itself changes, and the F-Droid repository is + unaffected. + ### Fixed - Reminders now arrive for every calendar you have switched on. A calendar that was hidden at system level — switched off in another calendar app, or never diff --git a/README.md b/README.md index f909377..f2e3b31 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ Reads, writes, and reminds — on top of the system calendar, with zero network access.

-CI +CI Android 10+ Kotlin + Compose Material 3 Expressive diff --git a/app/src/main/java/de/jeanlucmakiola/calendula/ui/settings/SettingsScreen.kt b/app/src/main/java/de/jeanlucmakiola/calendula/ui/settings/SettingsScreen.kt index c490375..c1efe32 100644 --- a/app/src/main/java/de/jeanlucmakiola/calendula/ui/settings/SettingsScreen.kt +++ b/app/src/main/java/de/jeanlucmakiola/calendula/ui/settings/SettingsScreen.kt @@ -56,6 +56,7 @@ import androidx.compose.material.icons.filled.Keyboard import androidx.compose.material.icons.filled.Language import androidx.compose.material.icons.filled.Notifications import androidx.compose.material.icons.filled.Palette +import androidx.compose.material.icons.filled.PrivacyTip import androidx.compose.material.icons.filled.Translate import androidx.compose.material.icons.filled.Tune import androidx.compose.material.icons.filled.UploadFile @@ -408,7 +409,9 @@ private fun LanguageRow(position: Position) { @Composable private fun AboutCard() { // The card layout lives in floret-kit (components.AboutCard); Calendula - // supplies its own logo, author and the source / licence / support links. + // supplies its own logo, author and the source / licence / privacy / support + // links. The privacy policy has to be reachable from inside the app, not just + // from the store listing, because Calendula touches calendar and contact data. AboutCard( logo = { AppLogo() }, appName = stringResource(R.string.app_name), @@ -424,6 +427,11 @@ private fun AboutCard() { label = stringResource(R.string.settings_license), url = stringResource(R.string.about_license_url), ), + AboutLink( + icon = Icons.Default.PrivacyTip, + label = stringResource(R.string.settings_about_privacy), + url = stringResource(R.string.about_privacy_url), + ), ), highlightLink = AboutLink( icon = Icons.Default.Favorite, diff --git a/app/src/main/res/values-ar/strings.xml b/app/src/main/res/values-ar/strings.xml index ce0c971..49c17a2 100644 --- a/app/src/main/res/values-ar/strings.xml +++ b/app/src/main/res/values-ar/strings.xml @@ -53,11 +53,11 @@ تعذّر مشاركة هذا الحدث. حذف الحدث؟ الحدث أُزيل من تقويمك ومن كل جهاز تتم مزامنته معه. - حذف الحدث المتكرر + حذف الحدث المتكرّر فقط هذا الحدث هذا وجميع الأحداث اللاحقة جميع الأحداث في السلسلة - تعديل الحدث المتكرر + تعديل الحدث المتكرّر تعذّر حذف الحدث Calendula يحتاج إلى صلاحية للكتابة لحذف الأحداث إلغاء @@ -108,7 +108,7 @@ الحدث يبقى كما هو الآن هل تريد تطبيق تذكيرك الافتراضي؟ هذا الحدث حُذف في هذه الأثناء، على سبيل المثال على جهاز آخر. لم يعد من الممكن حفظ تغييراتك. - المزيد من الخيارات + المزيد من الخانات علني الافتراضي مشغول @@ -153,7 +153,7 @@ %1$s حتى %2$s %1$s، %2$d مرّات طَبِّق الافتراضي - حدث مُتكرر + حدث متكرّر %1$s في %2$s تم القُبول @@ -166,7 +166,7 @@ ملغي متاح خاص - سري + سرّي المُنظِّم اختياري التذكير الافتراضي @@ -180,7 +180,7 @@ Android لا يعرض تذكيرات الأحداث من نفسه — بل يجب أن يقوم تطبيق تقويم بذلك. دع Calendula يتولى هذه المهمة. كل تذكير لأحداثك يصِل كإشعار، في الوقت المحدد تمامًا. هل تستخدم تطبيق تقويم ثانٍ؟ - إذا كان تطبيق آخر أيضًا ينشر تذكيرات، فستراهم مرتين — قم بإيقاف تشغيلهم هناك أو هنا. + إذا تطبيق آخر أيضًا ينشر تذكيرات، فستراهم مرتين — قم بإيقاف تشغيلهم هناك أو هنا. يمكنك تغييره في أي وقت شَغِّل التذكيرات ليس الآن @@ -200,7 +200,7 @@ البحث عن الأحداث مسح لا مزيد من الأحداث اليوم - لا توجد أحداث مطابقة ”%1$s“. + لا أحداث مطابقة ”%1$s“. ابحث عن أحداثك بالعنوان، الموقع أو الملاحظات. الرجوع البحث @@ -217,7 +217,7 @@ رجوع المظهر اللون الديناميكي - يتطلب Android 12 أو أحدث + يتطلب أندرويد ١٢ أو أحدث طريقة العرض الافتراضية ألوان تقويم ناعمة خفف ألوان التقويم والأحداث لتتناسب مع الثيم. قم بإيقافه لإظهار الألوان الخام من مصدر التقويم. @@ -227,7 +227,7 @@ اختر ملفًا… خط مخصص تعذّر قراءة هذا الملف كخط - يبدأ الأسبوع في + الأسبوع يبدأ في تلقائي تلقائي تنسيق الوقت @@ -243,4 +243,58 @@ إظهار إخفاء جدول + المنطقة الزمنية للجهاز + البحث عن المناطق الزمنية + جميع المناطق الزمنية + لا منطقة زمنية تطابق \"%1$s\" + %1$s توقيتك + الأحدث + أدخل رقمًا من ١ إلى ٩٩٩ + + (%d) لا دقائق + (%d) دقيقة واحده + %d دقيقتان + %d دقائق + %d دقيقة + %d دقيقة + + + (%d) لا ساعات + (%d) ساعة واحده + %d ساعتان + %d ساعات + %d ساعة + %d ساعة + + + (%d) لا أيام + (%d) يوم واحد + %d يومان + %d أيام + %d يوم + %d يوم + + + (%d) لا أسابيع + (%d) أسبوع واحد + %d أسبوعان + %d أسابيع + %d أسبوع + %d أسبوع + + يبدأ %1$s + ينتهي %1$s + الانتقال إلى اليوم + تحديث + اسم التطبيق + تعتيم الأحداث المكتملة + تعتيم الأحداث التي انتهت بالفعل في عرض الشهر والأسبوع + زر اليوم في شريط الأدوات + إظهار زر الانتقال إلى اليوم في شريط الأدوات بدلاً من زر عائم + اعرض Calendula كـ \"Calendar\" في مشغّل التطبيقات الخاص بك. فقط الاسم في المشغّل يتغير؛ وقد ينتقل الرمز إلى مكان جديد بعد التبديل. + تعتيم + الأحداث السابقة + مدى الجدول + مخصص… + أيام diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index 89b8563..629871c 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -354,7 +354,7 @@ Pomóż w tłumaczeniu Dodaj lub ulepsz tłumaczenie w Weblate Motyw, domyślny widok, pierwszy dzień tygodnia - Kolejność przycisku szybkiego przełączania oraz menu + Układ widoku miesiąca, przycisk szybkiego przełączania, kolejność menu Domyślne pola dla nowych wydarzeń Przypomnienia o wydarzeniach Urodziny i rocznice kontaktów @@ -515,4 +515,32 @@ Zachowaj dzisiejszy dzień na górze agendy i widżetu, nawet gdy nie ma już na dziś żadnych zadań. Urodziny Rocznice + Strefa czasowa urządzenia + Podąża za Twoją lokalizacją + Znajdź strefę czasową + Ostatnie + Wszystkie strefy czasowe + Żadna strefa czasowa nie pasuje do „%1$s” + %1$s Twojego czasu + Wpisz liczbę od 1 do 999 + Początek o %1$s + Koniec o %1$s + Dzisiaj + Przycisk „Dzisiaj” na pasku narzędzi + Pokaż przycisk skoku do dzisiaj na pasku narzędzi zamiast przycisku pływającego + Nazwa aplikacji + Wyświetlaj Calendula jako „Kalendarz” w menu aplikacji. Zmieni się tylko nazwa w menu; po przełączeniu ikona może pojawić się w innym miejscu. + Widok miesiąca + Styl widoku miesiąca + Strony + Jeden miesiąc wypełnia ekran. Przesuń w lewo lub w prawo, aby zmienić miesiąc. + Przewijanie miesięcy + Każdy miesiąc znajduje się pod własnym nagłówkiem, oddzielony od następnego niewielkim odstępem. + Tygodnie bez przerw + Tygodnie są ułożone jeden po drugim, każdy miesiąc płynnie łączy się z następnym, bez odstępu pomiędzy. + Podzielony + Zwięzła siatka wyróżnia dni z wydarzeniami a lista dla dnia wybranego dotknięciem wyświetla się poniżej. + Brak planów + Pokaż cały miesiąc + Pokaż wydarzenia dnia diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 3e24629..21c16fc 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -462,6 +462,7 @@ MIT by Jean-Luc Makiola Source + Privacy policy Support development Version %1$s Calendula app icon @@ -579,8 +580,9 @@ Add Quick Settings tile Add a “New event” tile to the Quick Settings panel. - https://gitea.jeanlucmakiola.de/makiolaj/calendula - https://gitea.jeanlucmakiola.de/makiolaj/calendula/src/branch/main/LICENSE + https://codeberg.org/jlmakiola/calendula + https://codeberg.org/jlmakiola/calendula/src/branch/main/LICENSE + https://jeanlucmakiola.de/calendula/privacy https://ko-fi.com/jeanlucmakiola https://weblate.dev.jeanlucmakiola.de/engage/calendula/ diff --git a/docs/RELEASING.md b/docs/RELEASING.md index 4bd4f03..4426944 100644 --- a/docs/RELEASING.md +++ b/docs/RELEASING.md @@ -73,7 +73,8 @@ Published version codes so far: `v0.1.0`→100 … `v1.0.0`→10000 … `v2.0.0` CI and release are split so a change is built once on its PR and only does release work when a merge actually cuts a release: -- **`ci.yaml`** (on `pull_request`) — lint + unit tests + a debug assemble (and +- **`ci.yaml`** (`.forgejo/workflows/`, on `pull_request`, **Codeberg**) — lint + + unit tests + a debug assemble (and a Trivy scan), once per PR. Docs/metadata-only PRs skip the Android build but still report a green `CI` check. - **`release.yaml`** (on push to `main`, plus `workflow_dispatch`) — a cheap @@ -91,9 +92,9 @@ release work when a merge actually cuts a release: Alongside F-Droid, each release is mirrored to the Codeberg repo (`jlmakiola/calendula`) as a plain download for users who don't want F-Droid. -Gitea already **push-mirrors** branches and tags to Codeberg, but releases -aren't git objects and don't sync, so the pipeline creates the release over the -Codeberg API and attaches `calendula_v.apk` + its `.sha256`. It's the +Codeberg **push-mirrors** branches and tags to Gitea, but releases aren't git +objects and don't sync in either direction, so the pipeline creates the release +over the Codeberg API and attaches `calendula_v.apk` + its `.sha256`. It's the same APK the F-Droid repo serves (same **app key**), so it adds no trust surface. The step is best-effort: a Codeberg outage never fails an already-published F-Droid release, and it skips cleanly if `CODEBERG_RELEASE_TOKEN` is @@ -109,6 +110,39 @@ build, the version bump, and tag/release creation, and just re-signs the existing F-Droid index with the configured repo key and re-uploads. Use this for key rotation or repo recovery without publishing a new app version. +## Two forges, one repo + +**Codeberg (`jlmakiola/calendula`) is canonical** — git, issues, PRs, tags and +releases. The self-hosted Gitea instance is build infrastructure: it holds the +signing key, publishes the F-Droid repo, and runs the release pipeline. Codeberg +push-mirrors `main` and tags to Gitea, and a bumped `versionName` arriving there +triggers `release.yaml` exactly as before. + +Workflows are separated by **directory**, not by conditionals. Forgejo looks in +`.forgejo/workflows` → `.gitea/workflows` → `.github/workflows` and stops at the +first that exists; Gitea doesn't know `.forgejo/` at all: + +| Directory | Runs on | Contains | Secrets | +| --- | --- | --- | --- | +| `.forgejo/workflows/` | Codeberg | `ci.yaml`, `translations.yaml` | **none** | +| `.gitea/workflows/` | Gitea | `release.yaml`, `renovate.yml` | signing key, F-Droid, Play, bot tokens | + +The line is drawn at **secrets, not at CI-vs-release**. That's what makes fork +PRs safe: everything a contributor can trigger lives in `.forgejo/` and can +reference no secret. Renovate stays on the Gitea runner *even though it opens +PRs on Codeberg* — it talks to Codeberg's API rather than moving its token onto +the contributor-facing runner. + +Two consequences worth remembering: + +- **`detect` 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 would re-cut a shipped release. +- **Any ref that exists only on Gitea gets deleted** by the mirror. That's + correct under Codeberg-canonical, but don't debug a "vanished" branch without + remembering it. + ## Secrets (Gitea → repo Settings → Actions → Secrets) | Secret | Purpose | diff --git a/docs/fdroid-official/de.jeanlucmakiola.calendula.yml b/docs/fdroid-official/de.jeanlucmakiola.calendula.yml index 86b95a7..aa9c737 100644 --- a/docs/fdroid-official/de.jeanlucmakiola.calendula.yml +++ b/docs/fdroid-official/de.jeanlucmakiola.calendula.yml @@ -18,9 +18,9 @@ Categories: - Calendar & Agenda License: MIT AuthorName: Jean-Luc Makiola -SourceCode: https://gitea.jeanlucmakiola.de/makiolaj/calendula -IssueTracker: https://gitea.jeanlucmakiola.de/makiolaj/calendula/issues -Changelog: https://gitea.jeanlucmakiola.de/makiolaj/calendula/src/branch/main/CHANGELOG.md +SourceCode: https://codeberg.org/jlmakiola/calendula +IssueTracker: https://codeberg.org/jlmakiola/calendula/issues +Changelog: https://codeberg.org/jlmakiola/calendula/src/branch/main/CHANGELOG.md Donate: https://ko-fi.com/jeanlucmakiola AutoName: Calendula diff --git a/fdroid-metadata/de.jeanlucmakiola.calendula.yml b/fdroid-metadata/de.jeanlucmakiola.calendula.yml index 2f6581e..61c667b 100644 --- a/fdroid-metadata/de.jeanlucmakiola.calendula.yml +++ b/fdroid-metadata/de.jeanlucmakiola.calendula.yml @@ -6,6 +6,6 @@ Summary: A modern Material 3 Expressive calendar for Android. Categories: - Time -SourceCode: https://gitea.jeanlucmakiola.de/makiolaj/calendula -IssueTracker: https://gitea.jeanlucmakiola.de/makiolaj/calendula/issues +SourceCode: https://codeberg.org/jlmakiola/calendula +IssueTracker: https://codeberg.org/jlmakiola/calendula/issues Donate: https://ko-fi.com/jeanlucmakiola diff --git a/renovate.json5 b/renovate.json5 index 103bd3d..378bf07 100644 --- a/renovate.json5 +++ b/renovate.json5 @@ -7,6 +7,23 @@ ":semanticCommits", ], + // `config:recommended` brings in mergeConfidence:age-confidence-badges, whose + // Age column is a Mend badge. Mend's Merge Confidence index only covers Maven + // Central: org.jetbrains.kotlin, junit, truth, turbine et al resolve, but + // every androidx/compose artifact lives on Google's Maven repo and comes back + // as a grey UNKNOWN — i.e. most of this project. Renovate already knows the + // real answer, since it derives release timestamps itself for the + // minimumReleaseAge rules below (Google Maven serves `last-modified` on its + // POMs), so take the age from there and leave Mend to the Confidence column, + // which still carries signal for the Maven Central half. + prBodyDefinitions: { + Age: "{{#if releaseTimestamp}}{{{newVersionAgeInDays}}} d{{else}}unknown{{/if}}", + }, + // Default heading links to the Merge Confidence docs; this column is ours now. + prBodyHeadingDefinitions: { + Age: "Age", + }, + // No automerge: a dependency bump goes through the same review (and, for // anything touching the build, the same on-device check) as a feature // before it can ride a release — see docs/RELEASING.md and the @@ -15,6 +32,22 @@ // One reviewable surface; the dashboard issue lists everything pending. dependencyDashboard: true, + + // The cooling-off periods below are advisory, not a gate. "flexible" still + // prefers a version that has cleared its window, but when every candidate is + // too young it opens the PR at the newest one anyway, so merging early stays + // a judgement call. (The default, "strict", would suppress the PR entirely + // until a release aged in.) A still-young branch carries a yellow + // `renovate/stability-days` check so it's visible which side of the line + // it's on; with automerge off, nothing acts on that check by itself. + // + // NOT "none": that short-circuits the candidate loop in filter-checks.ts, and + // that loop is what calls postprocessRelease — the only thing that fetches a + // Maven artifact's Last-Modified header. Skipping it leaves releaseTimestamp + // unset, which empties the Age column and quietly makes minimumReleaseAge and + // the stability check no-ops, since both need that timestamp to compare. + internalChecksFilter: "flexible", + labels: ["dependencies"], prConcurrentLimit: 5, prHourlyLimit: 0, @@ -25,11 +58,28 @@ // Gitea Actions workflows live under .gitea/workflows, not .github — extend // the github-actions manager (same syntax) to watch them too. + // `fileMatch` is deprecated; the replacement takes the regex delimited, and + // Renovate's config migration was already rewriting this on every run. "github-actions": { - fileMatch: ["^\\.gitea/workflows/[^/]+\\.ya?ml$"], + managerFilePatterns: ["/^\\.gitea/workflows/[^/]+\\.ya?ml$/"], }, packageRules: [ + // Cooling-off period, scaled by blast radius: how long a release should + // have been out (and un-yanked, un-hotfixed) before it's considered + // settled. Advisory only — see `internalChecksFilter` above. + { + matchUpdateTypes: ["major"], + minimumReleaseAge: "30 days", + }, + { + matchUpdateTypes: ["minor"], + minimumReleaseAge: "20 days", + }, + { + matchUpdateTypes: ["patch", "digest", "pin", "rollback"], + minimumReleaseAge: "10 days", + }, // material3 is deliberately pinned to the 1.5 *alpha* line for the // Expressive APIs (see gradle/libs.versions.toml). Follow the alpha train // but keep it in its own PR, reviewed in isolation; revisit the pin when @@ -52,5 +102,18 @@ ], groupName: "test dependencies", }, + // Last word on the PR table. The merge-confidence preset sets prBodyColumns + // from inside a packageRule of its own, and only for the datasources Mend + // supports — so a plain top-level prBodyColumns would lose to it for maven + // deps, and the Gradle wrapper / Actions / container bumps would keep the + // default columns and show no age at all. A rule declared after it wins, + // and gives every PR the same table. + // "Pending" earns its place under a flexible filter: when the bump lands on + // a version that has cleared its window but a newer one hasn't, that newer + // version is named here rather than silently withheld. + { + matchPackageNames: ["*"], + prBodyColumns: ["Package", "Type", "Change", "Age", "Pending", "Confidence"], + }, ], }