Files
LibreMail/CLAUDE.md
T
JMR-devandClaude Opus 4.8 535cbcb49a build(coverage): finish wiring JaCoCo unit-test coverage reporting
Completes the crash-interrupted #192 WIP (app/build.gradle.kts already had a
jacocoTestReport task and toolVersion pin recovered onto build-192-jacoco):

- Move the JaCoCo tool version into gradle/libs.versions.toml instead of a
  hardcoded string in app/build.gradle.kts, matching how every other plugin
  version in this repo is sourced.
- Fix the generated-code exclusion list against the real compileDebugKotlin
  output (verified by inspecting the compiled class tree): Room's
  KSP-generated `_Impl` DAOs/database and the Compose compiler's per-file
  ComposableSingletons holders are the only generated code that actually
  lands in classDirectories, since Hilt/Dagger's generated Java and AGP's
  BuildConfig/R/Manifest are compiled by a separate javac task this report
  never reads. Drop the blanket `**/*$$*` exclude the WIP had — it was
  silently discarding ~200 real classes' worth of coverage on Kotlin's own
  `$$inlined$` synthetic classes (e.g. Flow.map { ... } transforms in the
  repositories), which is hand-written logic, not generated boilerplate.
- Add Hilt_*/Dagger* prefix patterns so the (currently inert,
  belt-and-suspenders) Hilt exclusions are actually correct if the
  classDirectories scope ever changes.
- Add a minimal CI step to the existing unit-tests job that runs
  jacocoTestReport and uploads the XML+HTML report as a build artifact.
  No coverage threshold gate yet (a jacocoTestCoverageVerification rule is
  a natural follow-up once there's a baseline).
- Document the new :app:jacocoTestReport task in CLAUDE.md.

Verified on JDK 21: fast gate (assembleDebug, testDebugUnitTest,
compileDebugAndroidTestKotlin, lintDebug, ktlintCheck, detekt) plus
jacocoTestReport all pass, from both a warm and a `clean` build. The
report shows real signal (30% instruction / 38% line coverage) with no
generated classes leaking in.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-03 12:23:40 -05:00

3.6 KiB
Raw Blame History

CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

LibreMail is a GPL-3.0 Android email client (Kotlin, Jetpack Compose, Material 3). See @README.md for OAuth/account setup (Gmail, Outlook), the tech stack, and the offline-first architecture — this file only covers what isn't obvious from the code.

Build, test, lint

Use a JDK 17–21 for the Gradle daemon. AGP 9.2 does not support JDK 25 — if JAVA_HOME points at 25+, builds fail. Commands (PowerShell: use .\gradlew):

./gradlew :app:assembleDebug        # build debug APK
./gradlew :app:testDebugUnitTest    # JVM unit tests
./gradlew :app:lintDebug            # Android lint
./gradlew :app:ktlintCheck :app:detekt   # static analysis (CI's "Static analysis" gate)
./gradlew :app:jacocoTestReport     # JVM unit-test coverage (XML+HTML under app/build/reports/jacoco/)
# single unit test:
./gradlew :app:testDebugUnitTest --tests "org.libremail.data.SomeClassTest"

E2E/instrumented tests need a booted emulator: ./gradlew :app:connectedDebugAndroidTest, or via Gradle Managed Devices ./gradlew e2eGroupDebugAndroidTest (whole matrix) / ./gradlew api29DebugAndroidTest (one API level). The managed-device list in app/build.gradle.kts must stay in lockstep with the E2E matrix in .github/workflows/ci.yml.

Before treating a change as done, run the fast CI gate: assembleDebug + testDebugUnitTest + compileDebugAndroidTestKotlin + lintDebug + ktlintCheck + detekt (the /preflight skill does this). compileDebugAndroidTestKotlin compiles the androidTest source set that the rest of the gate skips, catching E2E/instrumented-test compile errors before they surface only in CI. ktlintCheck/detekt cover the test/androidTest source sets that lintDebug skips, so they catch style violations that would otherwise fail CI's Static analysis gate. Emulator E2E is left to CI unless asked.

Build-config gotchas

  • Built-in Kotlin (AGP 9.x). Kotlin compilation is handled by AGP's built-in Kotlin; the Kotlin version (2.4.0) is pinned via the root build.gradle.kts buildscript classpath. Never apply the org.jetbrains.kotlin.android plugin — it throws a ClassCastException against AGP 9's DSL. (The kotlin-android alias in libs.versions.toml exists but must not be used.) libs.versions.toml still supplies all library versions.
  • KSP, not KAPT for all annotation processing (Hilt, Room).
  • Room schemas are exported to app/schemas and validated by migration tests — commit schema changes.
  • OAuth client IDs come from secrets.properties (git-ignored) via BuildConfig; the build works without it (empty/placeholder values).

Code conventions

  • Sources live under app/src/{main,test,androidTest}/kotlin/; package root org.libremail (applicationId org.libremail.app).
  • Every source file starts with // SPDX-License-Identifier: GPL-3.0-or-later (or the <!-- ... --> form for XML/Markdown). All 117 current .kt files follow this.
  • kotlin.code.style=official.

Testing

JVM unit tests use JUnit4 + kotlin.test, Turbine for Flow, MockK for mocks, GreenMail for a real in-process IMAP/SMTP server, and coroutines-test. org.json is pulled in as a real dependency for unit tests because android.jar's version is a no-op stub.

Repo etiquette

  • Branch off main; branch names like feat-… / fix-…. PRs target main and must pass the CI passed gate.
  • Conventional Commits for commit subjects and PR titles: type(scope): summary (feat, fix, chore, …), matching existing history.