V3-12: crash reporting (code only)

shouldInitializeCrashReporting() and scrubExtra() are pure, fully unit-tested decision/scrubbing functions wired into sentry_flutter via beforeSend/beforeBreadcrumb. Config.crashReportingEnabled defaults off; the DSN is a compile-time --dart-define, not a preference. RecordingEngine gained an injected onUnexpectedStop callback, firing once when restoreAfterProcessDeath finds a trip still 'recording' at launch -- the process died without a user stop.

Marked partially done: the ticket's real acceptance criteria (a forced crash landing in a real Sentry project from a release build, inspecting a real payload) need a Sentry account and a release build this environment can't produce.
This commit is contained in:
2026-08-17 19:32:54 -05:00
parent 0e605ef174
commit be50448917
14 changed files with 443 additions and 9 deletions

View File

@@ -68,11 +68,13 @@ class RecordingEngine {
LiveTelemetry? liveTelemetry,
int Function()? clock,
Future<void> Function()? uploadPending,
void Function(String reason)? onUnexpectedStop,
}) : _repo = repository,
_source = locationSource,
_live = liveTelemetry ?? LiveTelemetry.instance,
_now = clock ?? (() => DateTime.now().millisecondsSinceEpoch),
_uploadPending = uploadPending;
_uploadPending = uploadPending,
_onUnexpectedStop = onUnexpectedStop;
final TripRepository _repo;
final LocationSource _source;
@@ -84,6 +86,11 @@ class RecordingEngine {
final Future<void> Function()? _uploadPending;
Timer? _uploadTimer;
/// V3-12: injected rather than importing a crash reporter directly, the same reasoning
/// as [_uploadPending] -- the engine stays free of any opinion about where a report
/// goes, and tests need no Sentry client.
final void Function(String reason)? _onUnexpectedStop;
/// Unbounded, exactly like the Kotlin `Channel(UNLIMITED)`. Appending is the only work
/// done on the fix path.
final List<TrackPoint> _pending = [];
@@ -240,6 +247,12 @@ class RecordingEngine {
final trip = await _repo.activeTrip();
switch (trip?.state) {
case TripState.recording:
// A trip still marked `recording` at launch means the previous process ended
// without ever calling stop() or pause() -- a crash, an OS kill, or a location
// permission revoked out from under the app. This is the failure V3-12 exists to
// surface: the recording stopped, silently, and nobody chose that.
_onUnexpectedStop?.call('process death mid-recording');
// Resume into a genuinely *new* segment: the time the process was dead is a real
// gap in the recording and must render as one.
//