Implements a no-regression JVM unit-test coverage gate (the maintainer chose this over an absolute 95% floor), scoped to the testable surface.
What changed
Scoped :app:jacocoTestReport — the denominator now excludes code that is structurally unreachable from a JVM unit test: Compose screen/component render code, Android framework entry points (*Activity/*Service/Application/*BackupAgent), Hilt DI (**/di/**), and the src/debug cold-open probe. Kept in scope: ViewModels, repositories, mappers, DAOs, utils, richtext, mail, reporting logic — and the six WorkManager Workers.
New :app:jacocoTestCoverageVerification gate — shares the exact same classDirectories/executionData/sourceDirectories as the report, with a BUNDLE / LINE / COVEREDRATIOminimum. Wired into both the check lifecycle task (local parity) and CI's unit-test job, so a coverage regression now fails the CI passed gate.
Baseline & floor
Baseline: 80.21% line (4838 / 6032 covered).
Floor set to 0.79 — ~1.2% below baseline so ordinary run-to-run noise doesn't red-flag it, but a real drop fails. Verified both ways: the gate passes at 0.79, and fails when temporarily raised to 0.85 (lines covered ratio is 0.80, but expected minimum is 0.85).
It's a manual ratchet for now: a comment in build.gradle.kts says to bump the floor up in the same PR whenever coverage rises materially.
This supersedes PR #292 (branch build-290-jacoco-scope), which reported 78% but excluded **/*Worker*. All six Workers (SyncWorker, BackfillWorker, PruneWorker, SendWorker, ReportPurgeWorker, ReportUploadWorker) are heavily unit-tested (construct-and-doWork()), so they are kept counted in both numerator and denominator; only their Hilt wiring (WorkManagerModule) is excluded, via **/di/**. Keeping the well-tested Workers in is why the baseline rose from #292's 78% to 80.21%. #292 can be closed as superseded.
Validation (no emulator — build-config change only)
:app:testDebugUnitTest, :app:jacocoTestReport, :app:jacocoTestCoverageVerification, :app:ktlintCheck (incl. ktlintKotlinScriptCheck over build.gradle.kts), :app:detekt — all green. Confirmed in the report XML: 203 ViewModel / 138 Repository / 22 Dao classes and all 6 Workers are present; di/coldopen/Activity/Service/BackupAgent/Compose-screens are absent.
Implements a **no-regression** JVM unit-test coverage gate (the maintainer chose this over an absolute 95% floor), scoped to the testable surface.
## What changed
- **Scoped `:app:jacocoTestReport`** — the denominator now excludes code that is structurally unreachable from a JVM unit test: Compose screen/component render code, Android framework entry points (`*Activity`/`*Service`/`Application`/`*BackupAgent`), Hilt DI (`**/di/**`), and the `src/debug` cold-open probe. Kept in scope: ViewModels, repositories, mappers, DAOs, utils, richtext, mail, reporting logic — **and the six WorkManager Workers**.
- **New `:app:jacocoTestCoverageVerification` gate** — shares the exact same `classDirectories`/`executionData`/`sourceDirectories` as the report, with a `BUNDLE` / `LINE` / `COVEREDRATIO` `minimum`. Wired into both the `check` lifecycle task (local parity) and CI's unit-test job, so a coverage regression now fails the `CI passed` gate.
## Baseline & floor
- **Baseline: 80.21% line** (4838 / 6032 covered).
- **Floor set to `0.79`** — ~1.2% below baseline so ordinary run-to-run noise doesn't red-flag it, but a real drop fails. Verified both ways: the gate **passes** at 0.79, and **fails** when temporarily raised to 0.85 (`lines covered ratio is 0.80, but expected minimum is 0.85`).
- It's a **manual ratchet for now**: a comment in `build.gradle.kts` says to bump the floor up in the same PR whenever coverage rises materially.
## Correction to #292 — Workers kept in scope
This **supersedes PR #292** (branch `build-290-jacoco-scope`), which reported 78% but excluded `**/*Worker*`. All six Workers (`SyncWorker`, `BackfillWorker`, `PruneWorker`, `SendWorker`, `ReportPurgeWorker`, `ReportUploadWorker`) are heavily unit-tested (construct-and-`doWork()`), so they are **kept counted** in both numerator and denominator; only their Hilt wiring (`WorkManagerModule`) is excluded, via `**/di/**`. Keeping the well-tested Workers in is why the baseline rose from #292's 78% to 80.21%. **#292 can be closed as superseded.**
## Validation (no emulator — build-config change only)
`:app:testDebugUnitTest`, `:app:jacocoTestReport`, `:app:jacocoTestCoverageVerification`, `:app:ktlintCheck` (incl. `ktlintKotlinScriptCheck` over `build.gradle.kts`), `:app:detekt` — all green. Confirmed in the report XML: 203 ViewModel / 138 Repository / 22 Dao classes and all 6 Workers are present; `di`/coldopen/Activity/Service/BackupAgent/Compose-screens are absent.
Closes #251
Closes #292
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Closes #290
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Implements a no-regression JVM unit-test coverage gate (the maintainer chose this over an absolute 95% floor), scoped to the testable surface.
What changed
:app:jacocoTestReport— the denominator now excludes code that is structurally unreachable from a JVM unit test: Compose screen/component render code, Android framework entry points (*Activity/*Service/Application/*BackupAgent), Hilt DI (**/di/**), and thesrc/debugcold-open probe. Kept in scope: ViewModels, repositories, mappers, DAOs, utils, richtext, mail, reporting logic — and the six WorkManager Workers.:app:jacocoTestCoverageVerificationgate — shares the exact sameclassDirectories/executionData/sourceDirectoriesas the report, with aBUNDLE/LINE/COVEREDRATIOminimum. Wired into both thechecklifecycle task (local parity) and CI's unit-test job, so a coverage regression now fails theCI passedgate.Baseline & floor
0.79— ~1.2% below baseline so ordinary run-to-run noise doesn't red-flag it, but a real drop fails. Verified both ways: the gate passes at 0.79, and fails when temporarily raised to 0.85 (lines covered ratio is 0.80, but expected minimum is 0.85).build.gradle.ktssays to bump the floor up in the same PR whenever coverage rises materially.Correction to #292 — Workers kept in scope
This supersedes PR #292 (branch
build-290-jacoco-scope), which reported 78% but excluded**/*Worker*. All six Workers (SyncWorker,BackfillWorker,PruneWorker,SendWorker,ReportPurgeWorker,ReportUploadWorker) are heavily unit-tested (construct-and-doWork()), so they are kept counted in both numerator and denominator; only their Hilt wiring (WorkManagerModule) is excluded, via**/di/**. Keeping the well-tested Workers in is why the baseline rose from #292's 78% to 80.21%. #292 can be closed as superseded.Validation (no emulator — build-config change only)
:app:testDebugUnitTest,:app:jacocoTestReport,:app:jacocoTestCoverageVerification,:app:ktlintCheck(incl.ktlintKotlinScriptCheckoverbuild.gradle.kts),:app:detekt— all green. Confirmed in the report XML: 203 ViewModel / 138 Repository / 22 Dao classes and all 6 Workers are present;di/coldopen/Activity/Service/BackupAgent/Compose-screens are absent.Closes #251
Closes #292
🤖 Generated with Claude Code
Closes #290