Observability — analytics & error tracking
Two vendor integrations, both real SDKs but config-gated off by default (no token/DSN dart-define → the provider never initializes; a clean no-op in dev and tests), plus a debug-only DevTools bridge. All three sit in the outside layer.
Mixpanel — product analytics
mixpanel_flutter: ^2.8.0, behindMixpanelEffect(app/lib/outside/effect_providers/mixpanel/) with aMixpanelEffectFakefor tests and aroute_observer.dartthat auto-tracks page views.- Config-gated: with no
MIXPANEL_TOKENdart-define the provider never initializes the SDK — events are dropped, not sent. - Privacy by construction: a per-install hashing salt anonymizes the distinct id, and the JTBD funnels emit bucketed values (e.g.
token_value_bucket: '1-3' | '4-9' | '10+'), never raw token amounts. - The JTBD funnels (fired from
TodayChoresBloc):today_section_shown,bounty_claimed,chore_completion_submitted,chore_completion_approved,approval_resolved. See Today JTBD funnels.
Sentry — crash / error observability
sentry_flutter: ^9.21.0+sentry_logging: ^9.21.0, behindSentryClientProvider(app/lib/outside/client_providers/sentry/) with a nullableConfiguration.- Config-gated: no
SENTRY_DSNdart-define → never initialized. - Capture path: the
LoggingIntegrationbridgespackage:logginginto Sentry — alog.severe(...)becomes a Sentry event. (This replaced an earlier "Sentry Effect"; manual capture now goes through logging.)
Bloc DevTools extension
packages/bloc_devtools_extension/— a DevTools plugin. The app posts bloc events viaDevtoolsBlocObserver(debug-only), serializing each@JsonSerializablebloc state. This is why the bloc-state contract is non-negotiable (see Architecture and Add a bloc & page).