Connect Stats Cache Invalidator to StatsAsyncNotifier via ref.invalidate
epic-activity-statistics-dashboard-state-and-realtime-task-006 — Wire the Stats Cache Invalidator to call ref.invalidate(statsNotifierProvider) after the debounce resolves. Ensure the invalidator holds a stable reference to the correct provider family instance (scoped by chapter and time window). Write integration logic so that when Supabase fires a realtime event, the debounce triggers, and the notifier re-fetches fresh stats from the repository without requiring any user gesture.
Acceptance Criteria
Technical Requirements
Execution Context
Tier 4 - 323 tasks
Can start after Tier 3 completes
Implementation Notes
Inject the invalidator with a `Ref` (not `BuildContext`) using Riverpod's `ref.read` pattern inside a non-UI provider or using `riverpod_annotation`. Pass the specific provider family arguments (chapterId, timeWindow) as constructor parameters to the invalidator at instantiation time — do not capture them via closure at invalidation time, as they may have changed. Use `ref.invalidate` (Riverpod 2.x) rather than the deprecated `ref.refresh` to avoid triggering an immediate synchronous fetch. Guard the `ref.invalidate` call with a try/catch or check `ref.exists(provider)` if Riverpod exposes it.
Keep the Supabase channel subscription in a separate service layer — the invalidator only handles the ref.invalidate call, not the subscription itself.
Testing Requirements
Write integration tests using flutter_test with ProviderContainer: (1) inject a mock Supabase realtime stream; (2) emit an INSERT event; (3) advance fake time by 800ms; (4) assert that statsNotifierProvider state is AsyncLoading then AsyncData; (5) assert re-fetch was called exactly once on the repository mock. Also test: invalidate called after provider disposal does not throw. Use ProviderContainer.overrides to replace the Supabase realtime dependency with a StreamController.
Supabase realtime channel subscriptions that are not properly disposed on screen close can accumulate in memory across navigation events, causing duplicate invalidation calls, ghost fetches, and eventual memory leaks on long sessions.
Mitigation & Contingency
Mitigation: Implement StatsCacheInvalidator as a Riverpod provider with an explicit ref.onDispose callback that cancels the realtime channel subscription. Write a widget test that navigates away and back multiple times and asserts that only one subscription is active at any given time.
Contingency: If subscription leaks are found in production, add a global subscription registry that enforces at-most-one subscription per channel key, and schedule a dispose sweep on app background events.
Debouncing rapid inserts may swallow the invalidation signal if the debounce window outlasts the Supabase realtime event delivery window, resulting in the dashboard showing stale totals after a bulk registration completes.
Mitigation & Contingency
Mitigation: Set the debounce window to 800ms (shorter than the typical Supabase realtime delivery latency of 1-2s for batched events) and ensure the leading-edge invalidation fires immediately while trailing duplicates are suppressed. Integration-test with a 20-record bulk insert.
Contingency: If debounce timing proves unreliable, replace debounce with a trailing-edge timer reset on each event and add a guaranteed invalidation 5 seconds after the last event regardless of subsequent events.