Commit Graph
399 Commits
Author SHA1 Message Date
Jason Ross fecd4d67dc Merge branch 'main' into feat-235-debug-report-accounts 2026-07-03 15:55:42 -05:00
Jason Ross 8779b439f7 Merge pull request #264 from JMR-dev/ci-262-autoupdate-tighten
ci(autoupdate): drop synchronize trigger, keep draft PRs updated
2026-07-03 15:51:30 -05:00
JMR-devandClaude Opus 4.8 3da0c3a580 ci(autoupdate): drop synchronize trigger, keep draft PRs updated
Narrow the pull_request trigger to opened/reopened/ready_for_review so
per-commit pushes to open PRs no longer storm the runners via a
rebase-of-all-PRs (PR_FILTER: all) on every synchronize event. Pin
PR_READY_STATE to "all" so draft PRs remain in scope for updates
triggered by push (main advancing) and opened.

Closes #262

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-03 15:49:37 -05:00
Jason Ross 402e19a707 Merge pull request #263 from JMR-dev/fix-255-crash-prompt-gating
fix(reporting): auto-prompt to submit a crash only on first re-open, for a legitimate <24h crash
2026-07-03 15:47:05 -05:00
Jason Ross 944bd45a1b Merge branch 'main' into fix-255-crash-prompt-gating 2026-07-03 15:31:03 -05:00
JMR-devandClaude Opus 4.8 6333dd4511 fix(reporting): gate startup crash prompt to a legitimate <24h crash, first re-open only
The auto-submit crash prompt over-triggered: it re-surfaced the newest saved
crash report on every launch, with no age bound, so a pre-update crash kept
popping "LibreMail crashed" long after the crash was fixed (#255).

Gate StartupReportViewModel.pendingCrash so a crash is auto-offered:
- first re-open only — dismiss() now persists a "surfaced" marker instead of an
  in-memory-only hide, so a report is offered at most once across launches; it
  stays in the store (still listed in Problem Reports) and only discard() deletes.
- < 24h only — inject a clock provider and filter to createdAtMillis within 24h.
- legitimate crash only — reports come solely from CrashReporter's uncaught-
  exception handler, so update / force-stop / user-close create none; made
  explicit and covered by a test.

The marker is a minimal additive `surfaced` flag on DebugReport (persisted in
storage JSON, kept out of the submission payload; a missing flag = not surfaced)
plus ReportStore.markSurfaced(id). Extracted StartupCrashPrompt from LibreMailApp
so the real dialog + gating is E2E-testable.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-03 15:17:22 -05:00
Jason Ross cb59417f59 Merge pull request #261 from JMR-dev/test-247-coverage-sync-workers-transport-auth
test(coverage): lane 2 — sync, workers, transport & auth to >=95%
2026-07-03 15:17:01 -05:00
Jason Ross 5a114517dc Merge main into test-247-coverage-sync-workers-transport-auth 2026-07-03 14:58:28 -05:00
JMR-devandClaude Opus 4.8 be0e699fcc test(coverage): lane 2 — sync, workers, transport & auth to >=95%
Test-only (zero production changes). Raises JVM unit-test LINE coverage
for the sync/worker, IMAP/SMTP/Graph transport, and OAuth packages:
data/sync 98.6%, mail 96.7%, auth 100.0% LINE.

New/extended cover:
- SendWorker outbox drain (SMTP/Graph, may-have-sent, SMTP fallback, staged
  attachments), MailConnectionFactory token cache/refresh, MailSyncer.syncAll,
  SendScheduler, MailBackfiller pre-existing-row refresh.
- ImapConnectionCache reuse + drop-retry, ImapClient fetchAttachment/setFlag/
  deleteMessage/idle + edge cases, GraphSender.send transport.
- OutlookAuthManager token exchange/refresh + failure branches, OAuth models.

Instruction/branch coverage stays lower (coroutine suspend-state synthetics
under synchronous mocks) — a known JaCoCo x coroutines limitation, not
untested logic; JaCoCo config is untouched (owned by the capstone lane).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-03 14:54:12 -05:00
Jason Ross 056ac69185 Merge main into feat-235-debug-report-accounts 2026-07-03 14:53:29 -05:00
Jason Ross 1e3cd7cae5 Merge pull request #260 from JMR-dev/ci-259-autoupdate-pr-trigger
ci(autoupdate): also trigger on pull_request so newly-opened PRs update immediately
2026-07-03 14:52:23 -05:00
JMR-devandClaude Opus 4.8 ef77a7559f ci(autoupdate): simplify pull_request trigger to any PR to main
Per repo-owner preference, drop the explicit types list and use the
default pull_request event set, keeping only the base-branch filter
(branches: [main]).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-03 14:49:39 -05:00
Jason Ross 339b4017ef Merge main into feat-235-debug-report-accounts 2026-07-03 14:48:13 -05:00
Jason Ross e03788da6d Merge main into ci-259-autoupdate-pr-trigger 2026-07-03 14:48:09 -05:00
Jason Ross f01ec66e85 Merge pull request #256 from JMR-dev/test-249-coverage-viewmodels-nonui
test(coverage): lane 4 — ViewModels & non-UI modules to >=95%
2026-07-03 14:47:40 -05:00
JMR-devandClaude Opus 4.8 9a3e3aa42d ci(autoupdate): also trigger on pull_request so newly-opened PRs update immediately
The workflow only fired on push to main, so a PR opened during a quiet
period (no subsequent merge to main) sat behind main until manually
updated. Add an opened/reopened/ready_for_review pull_request trigger;
synchronize is intentionally excluded to avoid re-running on every push,
including the autoupdate action's own branch updates.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-03 14:44:14 -05:00
Jason Ross 50f8cf2e2b Merge branch 'main' into test-249-coverage-viewmodels-nonui 2026-07-03 14:34:32 -05:00
JMR-devandClaude Opus 4.8 b5997f75fe test(coverage): lane 4 — ViewModels & non-UI modules to >=95%
Add JVM unit tests (test-only; no production changes) covering the in-scope
ViewModels + UI state holders and the reporting/push/power/contacts modules
for issue #249.

New ViewModel coverage: Drafts, Outbox, Signatures, SignatureEdit,
AccountSettings, AccountSetup, ManualSetup, ProblemReports, StartupReport,
plus gap-filling for Compose, Mailbox, Reader, Settings, ReportReview and
AppPassword (contacts autocomplete, inline images, send/refresh failure
branches, drawer/search hooks, state-holder value semantics).

New module coverage: AppLog, AppVersionProvider, ReportSubmitter,
ReportUploadWorker (reachable paths), CrashReporter.install, LogEntry,
IntentComposeParser, ContactsRepository, ContactsPermissionManager,
IdlePushManager, BatteryOptimizationManager (Context methods) and
AndroidBatteryStatusProvider.

Android-framework-bound classes with no JVM seam are deliberately left to the
instrumented suite: IdleService (foreground Service), ReportUploadScheduler
(WorkManager.getInstance is not statically mockable), the HTTP transmit path in
ReportUploadWorker (unreachable while BuildConfig.DEBUG_REPORT_ENDPOINT is
empty), and CrashReporter.terminate (calls exitProcess).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-03 14:26:41 -05:00
Jason Ross 81a362c30a Merge main into feat-235-debug-report-accounts 2026-07-03 14:10:07 -05:00
Jason Ross 77731e06e4 Merge pull request #254 from JMR-dev/test-246-coverage-repo-mappers-domain
test(coverage): lane 1 — repository, mappers & domain logic to >=95%
2026-07-03 14:09:34 -05:00
JMR-devandClaude Opus 4.8 e2606b7751 test(coverage): lane 1 — repository, mappers & domain logic to >=95%
Add JVM-only unit tests (74 across 4 new, purely-additive files) covering
the data/repository, data-mapper, and pure domain packages. No production
code is changed.

- AccountRepositoryImplTest: first tests for AccountRepositoryImpl — add/
  test/delete/observe + reset-backfill, success and rejected-LIST failure
  paths (class now 100% instruction & line).
- MailRepositoryImplCoverageTest: the MailRepositoryImpl methods/edges the
  existing suite skipped — observe-* flows, getMessage/getDraft, setStarred,
  deleteMessage, sendMessage + copyAttachments (incl. unreadable-URI skip),
  searchServer (all-accounts vs. filtered), and the account/row-gone
  fall-throughs.
- MappersTest: entity<->domain mappers not otherwise pinned, incl. the
  unknown-enum fallbacks and FetchedMessage id/uid rules.
- DomainModelCoverageTest: AccountSettings.signatureBlock branches,
  Signature.plainText, default-arg constructors, and display-name fallbacks
  (domain/model now 100% instruction & line).

Closes #246

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-03 13:55:51 -05:00
Jason Ross f2cbabb59a Merge main into feat-235-debug-report-accounts 2026-07-03 13:15:30 -05:00
Jason Ross e600122cc0 Merge pull request #252 from JMR-dev/docs-definition-of-done
docs(claude): require unit + latest-API E2E in the definition of done
2026-07-03 13:14:56 -05:00
JMR-devandClaude Opus 4.8 3903a4b4b1 docs(claude): run latest-API emulator E2E in preflight and require it for done
Make the latest-API-level emulator E2E (api36DebugAndroidTest, the
highest level in the E2E matrix and its Gradle Managed Device task) an
actually-run, required step:

- CLAUDE.md: preflight now runs api36DebugAndroidTest, and a change is
  not done until that E2E runs and passes locally (not merely compiles).
  The full multi-API matrix and the API 37 preview job stay CI's job.
- preflight skill: add the api36 E2E as the final step, note the
  emulator/managed-device precondition, and replace the old
  "don't run E2E locally" guidance so the two files agree.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-03 13:12:24 -05:00
JMR-devandClaude Opus 4.8 321d90432b docs(claude): drop local-emulator carve-out from definition of done
State plainly that a change isn't complete without passing unit tests
and E2E/instrumented tests covering it, with no softening about
running the emulator matrix locally being optional.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-03 13:01:47 -05:00
JMR-devandClaude Opus 4.8 d52cd2b4bf docs(claude): require unit + E2E tests in the definition of done
Codify that a task/PR isn't complete without both passing unit tests
and E2E/instrumented tests covering the change. Writing and committing
the E2E/instrumented test is required; only running it against a
booted emulator locally stays optional, since CI's E2E matrix covers
that.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-03 13:00:09 -05:00
Jason Ross 92a85c1f9e Merge main into feat-235-debug-report-accounts 2026-07-03 12:55:49 -05:00
Jason Ross b3e3ddd59d Merge pull request #241 from JMR-dev/build-192-jacoco
build: wire up JaCoCo code-coverage reporting
2026-07-03 12:55:16 -05:00
JMR-devandClaude Opus 4.8 4260a304bd feat(reporting): add PII-free account summary to debug reports
DiagnosticsCollector now includes one "<provider> (<authType>)" entry per account (the count is the
list size) in DebugReport.accounts, alongside the existing settings + recent-log capture. The provider
is a coarse bucket derived from the IMAP host (Gmail/Yahoo/iCloud/Outlook/AOL/Other) — never the raw
host or email — so no PII leaks; a custom domain buckets to "Other". Accounts are cached like settings
so crash reports (built on the crashing thread) include the last-known snapshot; accounts live in the
non-auth AccountDatabase, so reading them never blocks on the encrypted cache.

Recent device/app logs were already captured (RingLogBuffer) and serialize as the report's "logs".

Closes #235

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-03 12:53:58 -05:00
Jason Ross 9cd136f81b Merge main into build-192-jacoco 2026-07-03 12:43:05 -05:00
Jason Ross 7b52a9682d Merge pull request #244 from JMR-dev/ci-autoupdate-all-prs
ci(autoupdate): keep all open PRs up to date (drop the auto_merge filter)
2026-07-03 12:42:36 -05:00
JMR-devandClaude Opus 4.8 aad9d99ffc ci(autoupdate): keep all open PRs up to date (drop the auto_merge filter)
Closes #243

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-03 12:39:24 -05:00
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
JMR-dev 05dde9399c Merge remote-tracking branch 'origin/main' into continue-192 2026-07-03 12:11:26 -05:00
Jason Ross df6cbd4a76 Merge pull request #238 from JMR-dev/chore-hiltviewmodel-package
chore(ui): migrate hiltViewModel to its new androidx.hilt.lifecycle.viewmodel.compose package
2026-07-03 12:09:43 -05:00
JMR-devandClaude Opus 4.8 cbc9fc62ef wip: recover work from interrupted session (issue #192)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-03 12:04:36 -05:00
JMR-devandClaude Opus 4.8 7e69fcf185 chore(ui): migrate hiltViewModel to its new androidx.hilt.lifecycle.viewmodel.compose package
Clears the "hiltViewModel is deprecated; moved to package
androidx.hilt.lifecycle.viewmodel.compose" compile warnings across the 16 ui/**
files. Pure import swap: the old androidx.hilt.navigation.compose.hiltViewModel
inline-delegates to the new symbol, which is already transitively on the compile
classpath via the pinned hilt-navigation-compose:1.3.0 -- no dependency change,
identical signatures, behaviour byte-for-byte identical.

Closes #236

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-03 11:57:10 -05:00
Jason Ross 2a2db6d0eb Merge pull request #231 from JMR-dev/test-cache-lock-gate-coverage
test(sync): cover the cache-lock gate on SyncWorker/SendWorker + the provisioner await
2026-07-03 11:41:19 -05:00
Jason Ross 34de875601 Merge main into test-cache-lock-gate-coverage 2026-07-03 11:28:04 -05:00
Jason Ross 3168bfa59e Merge pull request #230 from JMR-dev/build-release-arm-only
build(release): build the release APK for ARM only (arm64-v8a + armeabi-v7a)
2026-07-03 11:27:35 -05:00
Jason Ross b76447b8cf Merge main into build-release-arm-only 2026-07-03 11:15:18 -05:00
Jason Ross 2a07b2423e Merge main into test-cache-lock-gate-coverage 2026-07-03 11:15:17 -05:00
Jason Ross 48fe8d2fb0 Merge pull request #229 from JMR-dev/fix-prune-backfill-cache-lock-gate
fix(sync): gate PruneWorker & BackfillWorker on the encrypted-cache lock
2026-07-03 11:14:48 -05:00
JMR-devandClaude Opus 4.8 6d75bf5c6d test(sync): cover the cache-lock gate on SyncWorker/SendWorker + the provisioner await
Locks in the "pre-auth DB entry point defers while the encrypted cache is locked"
invariant that had zero coverage (which is how the PruneWorker/BackfillWorker gap
in #224 slipped in). Adds SyncWorkerTest and SendWorkerTest (locked -> retry with
the Lazy DB deps never resolved; unlocked -> runs), and a DatabaseProvisionerTest
case proving prepareCache() suspends on an auth-bound resolvePassphrase until it
resolves.

IdleService shares the same guard but is an Android Service (its start path needs
startForeground/Context), so its gate is covered by the instrumented test (#226)
rather than a JVM unit test.

Closes #225

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-03 11:04:34 -05:00
Jason Ross e2da97a2ff Merge main into fix-prune-backfill-cache-lock-gate 2026-07-03 11:01:14 -05:00
Jason Ross 82ed7dad1a Merge pull request #223 from JMR-dev/perf-mailbox-page-folder-search
perf(mailbox): page the per-account folder view and search
2026-07-03 11:00:45 -05:00
JMR-devandClaude Opus 4.8 a62d5edfdd build(release): build the release APK for ARM only (arm64-v8a + armeabi-v7a)
Adds ndk.abiFilters = ["arm64-v8a", "armeabi-v7a"] to the release build
type only, so the release APK/AAB ship the two ARM ABIs (64-bit + 32-bit)
instead of a universal build. x86 and x86_64 are intentionally dropped.
defaultConfig and the debug type are left untouched: CI's E2E matrix runs
the debug build on x86_64 emulators and needs the x86_64 native libs
(incl. libsqlcipher.so).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-03 10:57:30 -05:00
JMR-devandClaude Opus 4.8 7b5819021a fix(sync): gate PruneWorker & BackfillWorker on the encrypted-cache lock
PruneWorker and BackfillWorker were the only two pre-auth background DB entry
points that opened the database without first checking
EncryptedCacheGuard.isCacheLocked(). With encryptCache + appLock both on and a
headless cold start where the user hasn't authenticated (WorkManager after
reboot, or the periodic backfill/prune window while locked), the first DAO call
runs prepareCache() -> resolvePassphrase() -> session.await(), parking the
worker thread until unlock and serializing other DB openers behind the held
prepareCache mutex. Self-heals on unlock, but wastes wakelock/battery and makes
zero progress while locked.

Switch both workers' MailPruner/MailBackfiller injection to dagger.Lazy and add
the isCacheLocked() guard before resolving it, mirroring SyncWorker/SendWorker.
Add JVM regression tests (locked -> retry with the Lazy dep never resolved;
unlocked -> runs; failure -> retry).

Closes #224

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-03 10:54:48 -05:00
Jason Ross 6eb1a74a5b Merge branch 'main' into perf-mailbox-page-folder-search 2026-07-03 10:48:36 -05:00
Jason Ross cbeac0f26f Merge pull request #222 from JMR-dev/ci-e2e-boot-retry
ci(e2e): retry the API-29 emulator boot to absorb the android-emulator-runner keyevent race
2026-07-03 10:35:58 -05:00