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

@@ -3,11 +3,22 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'src/app/providers.dart';
import 'src/config/config.dart';
import 'src/crash/crash_reporter.dart';
import 'src/ui/router.dart';
import 'src/ui/theme.dart';
void main() {
runApp(const ProviderScope(child: RipprApp()));
Future<void> main() async {
// Loaded early, once, purely to decide whether to talk to Sentry at all -- the
// decision has to be made before `runApp`, since `SentryFlutter.init` wraps it. The
// widget tree loads its own `Config` again in `RipprApp.initState` for everything
// else; SharedPreferences is memory-cached after the first read, so this costs nothing
// beyond a single extra map lookup.
WidgetsFlutterBinding.ensureInitialized();
final config = await Config.load();
await maybeInitCrashReporting(
config: config,
appRunner: () async => runApp(const ProviderScope(child: RipprApp())),
);
}
class RipprApp extends ConsumerStatefulWidget {