Skip to content

feat: add crash history with Android and iOS diagnostics - #158

Open
MaxMichel2 wants to merge 22 commits into
mainfrom
feat/crash-history
Open

MaxMichel2 wants to merge 22 commits into
mainfrom
feat/crash-history

Conversation

@MaxMichel2

@MaxMichel2 MaxMichel2 commented Nov 5, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

Related to #148.

  • Add devview-crashhistory-core and the CrashHistory overlay module for bounded, local crash history with list/detail screens, copy, text sharing and JSON export. Records stay on-device unless explicitly copied or shared; existing crash handlers are chained rather than replaced.
  • Capture Android/iOS uncaught exceptions and app-reported errors. Import Android ApplicationExitInfo history (including ANRs and system kills), and bridge MetricKit through spmForKmp: the async API on iOS 27+, legacy delivery on iOS 15–26.
  • Harden persistence and imports with serialized normal storage operations, generation-aware Clear, persistence-gated acknowledgements, stable diagnostic IDs, positive/updatable retention, bounded traces and unique atomic-write temporary files. Exclude iOS storage from backup on a best-effort basis; isolate export files and report sharing failures.
  • Integrate the modules into the sample, API signatures, documentation, changelog, Dokka and coverage aggregation. Use Xcode 27 runners for iOS-binding/build/publication jobs; move release Detekt to Ubuntu.

Compatibility: iOS builds require Xcode 27+; runtime support starts at iOS 15. The bridge uses system MetricKit/CryptoKit without an external Swift package. Downstream Gradle-linked Kotlin/Native executables need the documented Swift-runtime linker configuration; Swift host-app linking was verified with the sample.

Scope: the full committed branch against main (22 commits, 70 files). No predecessor PR, inherited layer or stack dependency.

Validation

Checks below were executed during implementation and cover the final production code; subsequent commits only recorded/removed verification reports.

  • Android host tests: 61 passed (54 core, 7 UI-module tests), zero failures/errors/skips.
  • Architecture: 14 Konsist tests passed.
  • Lint/build: detektFull and :sample:androidApp:assembleDebug passed. Android tasks used -Pandroidx.baselineprofile.skipgeneration.
  • iOS native tests: 12 core + 4 UI-module unit tests passed on both iOS 26.5 and iOS 27.0 simulators with Xcode 27. These are not instrumented UI tests.
  • Native build/link: clean parallel device/simulator Swift builds, shared-framework linking and sample Xcode builds passed. Deterministic crash-ID Swift self-check passed.
  • iOS 27 manual smoke: uninstrumented sample launched and stayed alive; reported-error details, clipboard copy, text/JSON share-sheet presentation, valid JSON export, crash persistence across relaunch without reinstall, and Clear persistence were verified. Temporary logs verified entry into the iOS 27 MetricKit branch and async sequence setup, then were removed.
  • Privacy/docs: iOS backup-exclusion attribute readback was verified; zensical build passed with two existing unrelated link warnings.

Remaining checks and limitations

  • Real OS MetricKit diagnostic delivery was not observed; async subscription setup and sample crash persistence do not prove delivery.
  • Android instrumented/device tests, external recipient transfer, Save to Files, iPad presentation and a separately published-artifact consumer were not exercised.
  • Fatal-handler persistence and backup exclusion remain best-effort; fatal writes can race with Clear. Records are not redacted or symbolicated.
  • Detekt reports existing type-resolution warnings. The committed Metalava signature files retain two end-of-file blank-line warnings from git diff --check.
  • Knowledge-graph refresh was attempted but refused to overwrite existing semantic data; the curated graph was preserved unchanged.

RetriggerView in GreptileConfidence Score: 4/5

Fix the report queue before merging; it can lose reports even when the configured history has room.

Findings

  1. P1 Queued reports disappear ▶
  2. P2 Old crashes show new versions ▶
  3. P2 Earlier attachments can disappear ▶

Summary

Adds local crash history for Android and iOS.

  • Apps can keep a bounded record of errors and uncaught crashes on the device.
  • Android history adds selected reports about earlier crashes and system exits.
  • iOS crash history imports MetricKit diagnostics from the versions the app supports.
  • The Crash History overlay lets people browse, clear, and share saved records.
  • Project setup and docs show how to add Crash History and build its iOS bridge.

Acknowledged limits:

  • MaxMichel2 explicitly acknowledges fatal writes racing Clear as a best-effort limitation in the PR description.
  • MaxMichel2 explicitly lists real MetricKit delivery and recipient transfers as validation gaps in the PR description.
  • MaxMichel2 explicitly states the Xcode 27 and downstream Swift-linking compatibility requirements.
  • The source explicitly defers multi-window iOS sharing support.

Source review only. No runtime tests or UI captures were performed.

Diagram

%%{init: {'theme': 'neutral'}}%%
flowchart LR
    A[Uncaught exception] --> E[CrashRecorderEngine]
    B[App-reported error] --> E
    C[Android exit history] --> E
    D[iOS MetricKit bridge] --> E
    E --> F[Local JSON records]
    E --> G[Records StateFlow]
    G --> H[Crash list and details]
    H --> I[Copy or share]
Loading

Reviews (1) · Last reviewed commit: ":memo: Remove completed iOS 27 verificat..." · Reviewed by Greptile

Comment on lines +157 to +159
pending.update {
it.copy(
reports = (it.reports + report).takeLast(n = MAX_PENDING_REPORTS)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Queued reports disappear

record() drops queued reports after 20 entries even when recording has started with a larger maxRecords. With maxRecords = 100, for example, 50 reports arriving while an OS import holds the storage lock leave only the last 20 to save. The others never reach disk. Use the configured limit after startup, and reserve the fixed 20-entry limit for reports made before startup.

timestampMillis = exit.timestamp,
processId = exit.pid,
trace = readTrace(exit = exit),
metadata = metadata

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Old crashes show new versions

Android history imports attach the current launch's metadata to earlier exits. After an app update, a crash from the previous version is shown and exported as a crash in the new version. This can send testers looking for a bug in the wrong release. Preserve historical values when available; otherwise show unknown values or clearly label these fields as belonging to the current launch.

Comment on lines +31 to +33
val directory = NSTemporaryDirectory() + EXPORT_DIRECTORY
val files = NSFileManager.defaultManager
files.removeItemAtPath(path = directory, error = null)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Earlier attachments can disappear

Each iOS export deletes every earlier export, and repeated exports reuse the same path. If an earlier recipient still needs the file, a later export can remove or replace its attachment. Give each export a unique filename and remove only aged files, as the Android implementation does.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant