Integrate an SDK
Feature matrix
October 2026: Android, iOS and web SDK 0.3, Flutter package 0.4. Use it to decide which hooks a platform needs before you promise a bug will replay.
tested implemented and covered by automated tests or the recorded end-to-end run · implemented implemented, not tested here · partial works with a documented limitation · no not implemented or planned.
The Android, iOS and web end-to-end runs were recorded on 2026-10-21 (Pixel 6 API 35 emulator, iPhone 18 Pro simulator on iOS 27, Chromium 153). Flutter E2E figures come from the earlier measurement in the Flutter README; its unit suites were re-run, its device E2E was not.
Capture
| Feature | Flutter | Android | iOS | Web |
|---|---|---|---|---|
| Bounded flight recorder (20k events / 5 min, launch + periodic checkpoints) | tested | tested | tested | tested |
Manual capture (capture) | tested | tested | tested | tested |
| Automatic capture: uncaught errors | tested (FlutterError.onError, PlatformDispatcher.onError) | tested (JVM uncaught, chained handler) | partial (NSException via chained handler; Swift traps/signals not captured) | tested (error, unhandledrejection) |
| Crash capsule persisted, uploaded next launch | n/a (Dart errors do not end the process) | tested | tested | n/a (errors do not end the page) |
Assertions check / fail, reportError | tested | tested | tested | tested |
| Pointer capture | tested | tested (Window.Callback, dp) | tested (explicit BugCapsuleWindow / recordTouches) | tested (Pointer Events, target descriptor) |
| Semantic actions | tested | tested (Views + Compose) | tested (UIKit + SwiftUI) | tested (core + React) |
| Tracked text, sensitive fields | tested | tested (password input types forced) | tested (isSecureTextEntry forced) | tested (password inputs, data-bugcapsule-sensitive) |
| Key events | — | tested (Back, key event) | — | — (tracked inputs only) |
| Navigation | tested (GoRouter) | tested (Jetpack Navigation) | tested (UINavigationController proxy, explicit, SwiftUI) | tested (History API; React Router listener) |
| Lifecycle | tested | tested | tested | tested (visibility, pagehide/pageshow) |
| State | tested (BLoC, Riverpod, Provider, GetX, MobX) | tested (registered serializers, ViewModels) | tested (registered state objects) | tested (registered serializers, React hook) |
| Storage checkpoints (allowlisted) | tested (SharedPreferences) | tested (SharedPreferences, Preferences DataStore) | tested (UserDefaults) | tested (localStorage, sessionStorage); implemented (IndexedDB adapters: unit-level only) |
| HTTP capture | tested (Dio, package:http) | tested (OkHttp; Retrofit through it) | tested (URLSession via URLProtocol); implemented (Alamofire through an instrumented configuration, documented, not tested) | tested (fetch, XMLHttpRequest) |
| Bounded body capture, streams untouched | tested | tested | tested | tested (clone() read up to the limit) |
In-flight requests at capture (http.pending) | — | tested | tested | — |
| Redaction before persistence (shared conformance fixtures) | tested | tested | tested | tested |
| Remote config, kill switch, sampling | tested | tested | tested | tested |
| Rejected ingest key | stops capture, purges queue | stops capture, keeps queue | stops capture | stops capture, purges queue |
| Offline outbox (10 / 10 MiB / 7 days, backoff, Retry-After) | tested | tested | tested | tested (IndexedDB, cross-tab safe) |
| Runtime + capability metadata in capsules | tested | tested | tested | tested |
Replay
| Feature | Flutter | Android | iOS | Web |
|---|---|---|---|---|
| Runs the real app code | tested | tested (instrumentation, in-process engine) | tested (XCUITest launches the app, in-app engine) | tested (Playwright, in-page engine) |
| Pointer replay | tested | tested (sendPointerSync) | — (input.pointer.replay: unsupported, material pointer.unsupported) | tested (trusted Playwright input; mismatch/ambiguity reported) |
| Semantic action replay | tested | tested | tested | tested |
| Tracked text replay | tested | tested | tested | tested |
| Lifecycle replay | tested | tested (ActivityScenario states) | partial (posted notifications) | partial (synthetic visibility events) |
| Checkpoint restoration before app code | tested | tested (runner onCreate, before Application.onCreate) | tested (in BugCapsule.start()) | tested (init script + init()) |
| Late / failed restoration reported | tested | tested | tested | tested |
| Deterministic network replay (key + occurrence, latency, errors, cancel) | tested | tested | tested | tested (fetch + XHR) |
| Strict network (unrecorded never reaches real services) | tested | tested | tested | tested (+ runner blocks escaped requests) |
| Idle compression | tested | tested | tested | tested |
| Clock / random hooks (explicit only) | tested | tested | tested | tested |
| Production replay protection | tested (release builds) | tested (androidTest runner + debuggable) | tested (#if DEBUG + launch argument) | tested (allowReplay + runner bindings) |
| Attempt isolation | relaunch | pm clear | uninstall | fresh browser context |
| Physical devices | partial (iOS needs --lan) | implemented (adb device path, not tested) | — (simulator only; signing/devicectl planned) | n/a |
| Browsers | n/a | n/a | n/a | tested Chromium; implemented capture in Firefox/Safari (untested); no replay elsewhere |
| Cross-runtime replay | — | — | — | — (CLI reports INCOMPATIBLE) |
CLI and project
| Feature | Flutter | Android | iOS | Web |
|---|---|---|---|---|
inspect / run / verify / regress add / regress run | tested | tested | tested | tested |
| Source-revision replay (git worktree pin) | tested | implemented (generic; E2E used a scratch git copy) | implemented (generic; not exercised) | tested (scratch git copy: pinned REPRODUCED, working tree FIXED) |
| Prebuilt artifacts | — | implemented (--apk, --test-apk) | implemented (--xctestrun) | tested (--app-url) |
| Per-attempt token, attemptId, stale-report rejection, Origin refusal | tested | tested | tested | tested |
| Timeouts, Ctrl-C cleanup | tested | tested | tested | tested |
| Upload, run results, triage, sharing | tested | tested | tested | tested (CORS) |
| Filter capsules by platform | tested | tested | tested | tested |
Recorded end-to-end results (this session)
| Step | Android (Pixel_6_2, API 35) | iOS (iPhone 18 Pro sim, iOS 27) | Web (Chromium 153) |
|---|---|---|---|
| Capture + upload | race crash, null price, legacy cart, coupon | coupon assertion, race NSException, legacy cart | race, legacy cart, discontinued item, coupon |
run | REPRODUCED 3/3 (independent re-run 2/2) | REPRODUCED 3/3 | REPRODUCED 3/3 |
verify without fix | STILL FAILING 3/3 | STILL FAILING 3/3 | STILL FAILING 3/3 |
verify with isolated fix | FIXED 3/3 (independent re-run 2/2) | FIXED 3/3 | FIXED 3/3 |
| Restoration control (checkpoint removed) | — | NOT REPRODUCED 0/1 | NOT REPRODUCED 0/2 |
regress run | fail without fix, pass with fix | fail without fix, pass with fix | fail without fix, pass with fix |
| Offline capture → restart → upload | tested (airplane mode) | tested (BugCapsule unreachable) | tested (blocked uploads, reload) |
Shared verdict behaviour in all runtimes: a fix that makes the app send requests the recording never saw yields INCONCLUSIVE, never FIXED (strict network). This was observed for the web discontinued-item fix and the Android null-price and legacy-cache fixes.
Which hooks each platform needs most
| Platform | Don't skip |
|---|---|
| Flutter | Serializers for BLoC/Riverpod state you want restored from periodic checkpoints; clock.now(). |
| Android | OkHttp interceptor last; storage allowlists; sensitive = true for Compose password fields. |
| iOS | Semantic actions for every important tap (no pointer replay); BugCapsuleURLSession in integrations; check before code that traps. |
| Web | mode: 'qa' in QA builds; data-bugcapsule-action on clickable elements; your origin allowed by your BugCapsule team; allowReplay only in test builds. |