high priority medium complexity integration pending integration specialist Tier 4

Acceptance Criteria

StatsBloc subscribes to the Supabase Realtime channel for the activities table (filtered by organization_id) immediately on BLoC instantiation
Realtime subscription filter scopes to the current user's organization_id — no cross-org events received
On receiving an INSERT event for a new activity: StatsCacheManager.invalidate(cacheKey) is called for the current userId + filter combination, then LoadCoordinatorStats event is added to self
Auto-refresh only triggers for INSERT events — UPDATE and DELETE events on activities do not trigger a reload in this task
Realtime stream errors (network drop, JWT expiry) are caught and logged to verbose output — they do NOT emit StatsError state
After a Realtime error, the subscription is re-established automatically with exponential backoff (max 3 retries, then silent failure)
StatsBloc.close() cancels the StreamSubscription and unsubscribes the Supabase channel before calling super.close()
Rapid activity inserts (e.g. bulk registration) are rate-limited: at most one auto-reload per 10 seconds via a cooldown flag or debounce
Auto-refresh does not interrupt an already in-flight manual reload — if StatsLoading is the current state, the Realtime event is queued and processed after the current load completes
Unit tests can inject a mock Realtime stream so the subscription can be tested without a live Supabase connection

Technical Requirements

frameworks
Flutter
BLoC
flutter_bloc
apis
Supabase Realtime (WebSocket subscription on activities table)
Supabase Auth (JWT for channel authentication)
data models
activity
performance requirements
Realtime subscription overhead must not increase BLoC memory usage by more than 1MB
Auto-reload debounce window of 10 seconds prevents excessive Supabase queries during bulk activity imports
StreamSubscription must be cancelled within 100ms of BLoC.close() to prevent memory leaks
security requirements
Supabase RLS enforced on Realtime channel — users only receive INSERT events for rows they own or have coordinator access to
JWT validated on every channel subscription renewal — expired token triggers re-authentication before re-subscribing
No sensitive activity content transmitted via Realtime payload — only row id and status; full data fetched via repository on reload

Execution Context

Execution Tier
Tier 4

Tier 4 - 323 tasks

Can start after Tier 3 completes

Implementation Notes

Inject the Realtime stream as a Stream parameter in the BLoC constructor (or via a factory that wraps the Supabase client) so it can be mocked in tests. Pattern for debounce without bloc_concurrency: use a `DateTime? _lastAutoRefresh` field and check `DateTime.now().difference(_lastAutoRefresh) > 10s` before adding the reload event. For exponential backoff on stream errors, use a simple retry counter in the stream error handler with `Future.delayed(Duration(seconds: pow(2, retryCount).toInt()))`.

The StreamSubscription should be stored as `late StreamSubscription _activityStream` and cancelled in close(). Do NOT use a Timer for polling — Realtime is push-based and more efficient. Confirm with the team whether the Realtime channel should also react to activity DELETE events (e.g. coordinator cancelling a registration) — this task scopes to INSERT only but the channel should be set up to allow easy extension.

Testing Requirements

Unit tests using flutter_test + bloc_test with a mock Supabase Realtime stream (StreamController). Test cases: (1) BLoC subscribes to stream on creation, (2) INSERT event triggers cache invalidation + LoadCoordinatorStats, (3) UPDATE event does NOT trigger reload, (4) stream error is caught and does not emit StatsError, (5) two INSERT events within 10 seconds produce only one reload, (6) INSERT during in-flight load is queued and processed after load completes, (7) BLoC.close() cancels StreamSubscription (verify via mockito verify(subscription.cancel())), (8) auto-reload produces [StatsLoading, StatsLoaded] sequence. Integration test: verify Supabase RLS prevents receiving another org's activity events using two test org accounts.

Component
Coordinator Statistics BLoC
infrastructure medium
Epic Risks (3)
medium impact medium prob technical

fl_chart's default colour palette may not meet WCAG 2.2 AA contrast requirements when rendered on the app's dark or light backgrounds. If segment colours are insufficient, the donut chart will fail accessibility audits, which is a compliance blocker for all three organisations.

Mitigation & Contingency

Mitigation: Define all chart colours in the design token system with pre-validated contrast ratios. Run the contrast-ratio-validator against every chart colour during the adapter's unit tests. Use the contrast-safe-color-palette as the source palette.

Contingency: If a colour fails validation, replace with the nearest compliant token. If activity types exceed the available token set, implement a deterministic hashing algorithm that maps activity type IDs to compliant colours.

medium impact medium prob technical

StatsBloc subscribing to the activity registration stream creates a long-lived subscription. If the subscription is not disposed correctly when the dashboard is closed, it will cause a stream leak and potentially trigger re-fetches on a disposed BLoC, resulting in uncaught errors in production.

Mitigation & Contingency

Mitigation: Implement subscription disposal in the BLoC's close() override. Write a widget test that navigates away from the dashboard and asserts no BLoC events are emitted after disposal.

Contingency: If leaks are detected in QA, add a mounted check guard before emitting states from async callbacks, and audit all other BLoC stream subscriptions in the codebase for the same pattern.

low impact low prob scope

PersonalStatsService's Phase 4 gamification data structure is designed against an assumed future schema. If the Phase 4 Spotify Wrapped feature defines a different data contract when it is developed, the structure built now will require a breaking change and migration.

Mitigation & Contingency

Mitigation: Document the contribution data structure with explicit field semantics and versioning comments. Keep the Phase 4 fields as optional/nullable so they do not break existing consumers if the schema evolves.

Contingency: If the Phase 4 schema diverges significantly, the personal stats data can be re-mapped in a thin adapter layer without changing PersonalStatsService's core implementation.