critical priority high complexity backend pending backend specialist Tier 1

Acceptance Criteria

Receiving LoadSummary immediately emits WrappedSummaryLoading before any async work
On success, emits WrappedSummaryLoaded with non-empty slides list and annotated milestones
Milestone detection service is called with the raw aggregation result before the loaded state is emitted
On SocketException or timeout, the handler reads from the offline cache and emits WrappedSummaryOffline if a cached entry exists
If no cache exists and the network call fails, WrappedSummaryError is emitted with error code `NO_DATA`
If the aggregation service returns an unexpected schema, WrappedSummaryError is emitted with error code `PARSE_ERROR`
Concurrent LoadSummary events are debounced or the previous stream is cancelled via `transformer: restartable()`
Successful response is written to the offline cache before emitting WrappedSummaryLoaded
Handler is registered in the BLoC constructor with `on<LoadSummary>(_onLoadSummary, transformer: restartable())`
No raw Supabase exceptions leak out of the handler — all errors are mapped to domain error codes

Technical Requirements

frameworks
Flutter
BLoC (flutter_bloc)
bloc_concurrency (restartable transformer)
apis
Annual stats aggregation service (internal)
Milestone detection service (internal)
Offline cache service (internal)
Supabase REST/RPC endpoint for aggregated stats
data models
WrappedSummarySlide
WrappedMilestone
SummaryPeriod
CachedSummary
performance requirements
Total handler execution (network + milestone annotation) must complete within 5 seconds on a 4G connection
Milestone annotation must not block the event loop — run in an isolate if processing > 200 items
security requirements
Supabase RLS must restrict aggregation queries to the authenticated user's own data
Cache must be encrypted at rest using flutter_secure_storage or equivalent

Execution Context

Execution Tier
Tier 1

Tier 1 - 540 tasks

Can start after Tier 0 completes

Implementation Notes

Use `bloc_concurrency` package's `restartable()` transformer to automatically cancel in-flight requests on repeated events. Structure the handler as: (1) emit Loading, (2) try aggregation fetch + milestone annotation, (3) on success write cache then emit Loaded, (4) on catch check cache and emit Offline or Error. Inject all dependencies (aggregation service, milestone service, cache) via constructor to keep the BLoC testable. Define typed failure classes (`AggregationFailure`, `ParseFailure`) in a separate domain layer rather than catching raw exceptions in the BLoC.

Milestone annotation should be a pure function: `List detectMilestones(AggregationResult result)` — easy to unit test in isolation.

Testing Requirements

Unit tests with flutter_test and bloc_test. Test cases: (1) emits [Loading, Loaded] on successful fetch, (2) emits [Loading, Offline] when network fails and cache exists, (3) emits [Loading, Error(NO_DATA)] when network fails and no cache, (4) emits [Loading, Error(PARSE_ERROR)] on malformed response, (5) concurrent LoadSummary events cancel the previous in-flight request. Mock the aggregation service, milestone service, and cache service. Integration test: wire real Supabase test environment and verify milestone annotations match expected values.

Component
Wrapped Summary BLoC
service medium
Epic Risks (3)
medium impact medium prob technical

If the device transitions between online and offline states while the user is mid-session in the wrapped screen, the BLoC may emit conflicting state transitions (loaded → error → offline) that cause visual flickering or an inconsistent UI state such as showing the offline banner over an already-loaded summary.

Mitigation & Contingency

Mitigation: Implement a connectivity stream listener in the BLoC that only triggers a state re-evaluation when transitioning from online to offline, not on every connectivity event. Once a summary is in the Loaded state, the BLoC should not transition to error/offline unless the user explicitly requests a refresh. Store the last-loaded data in BLoC state so it survives connectivity changes.

Contingency: If state flickering is observed in testing, add a minimum 3-second debounce on connectivity state changes before the BLoC reacts, and display a non-blocking top banner rather than replacing the entire screen state.

high impact medium prob integration

The push notification deep-link to the wrapped-summary-screen must work correctly whether the app is in the foreground, background, or terminated state. Handling all three app launch states on both iOS and Android is a common source of edge-case bugs, particularly when authentication state must be restored before the deep link can be resolved.

Mitigation & Contingency

Mitigation: Implement deep-link handling through the existing notification-deep-link-handler component which already manages app-state-aware routing. Define the wrapped-summary route in the navigation config early in the epic so the router is ready before notification dispatch is wired. Test all three app states (foreground, background, terminated) explicitly in the QA checklist.

Contingency: If terminated-state deep-linking fails on specific platforms, fall back to launching the app to the home screen with an in-app notification banner prompting the user to open their summary, rather than direct deep-link navigation.

high impact low prob technical

The wrapped-summary-screen manages a large number of AnimationController instances (one or more per slide) via the wrapped-animation-controller. If disposal is not triggered correctly when the user exits mid-flow (e.g., via system back gesture or deep-link away), memory leaks will accumulate across session navigation.

Mitigation & Contingency

Mitigation: Implement screen disposal via Flutter's dispose() lifecycle method calling a single wrapped-animation-controller.disposeAll() method that iterates the named controller registry. Write a test that navigates to the screen, starts animations, then navigates away and verifies no active AnimationController listeners remain using Flutter's test binding.

Contingency: If disposal bugs are detected in production via memory profiling, patch by converting all AnimationControllers to use AutomaticKeepAliveClientMixin false and wrap each slide in a widget that disposes its own controller when removed from the widget tree.