high priority medium complexity backend pending backend specialist Tier 2

Acceptance Criteria

Receiving ShareSlide while not in WrappedSummaryLoaded state is a no-op
Handler emits a transient `WrappedSummarySharing` state (or `isSharing: true` variant of Loaded) immediately to show a loading indicator
Handler delegates screenshot capture and share sheet invocation entirely to the injected summary share service
On share success (user completed or dismissed share sheet), handler emits the original WrappedSummaryLoaded state (restoring normal UI)
On share failure (screenshot error, share service unavailable), handler emits WrappedSummaryShareError sub-state with `slideIndex` and `failureReason`
After WrappedSummaryShareError the screen can dismiss the error and the previous Loaded state is recoverable
Concurrent ShareSlide events are dropped while a share is already in progress (droppable transformer)
The BLoC does not import any Flutter rendering or image libraries directly — all platform work is behind the share service interface

Technical Requirements

frameworks
Flutter
BLoC (flutter_bloc)
bloc_concurrency (droppable transformer)
apis
Summary share service (screenshot capture + platform share sheet, internal)
data models
WrappedSummarySlide
performance requirements
Transient sharing state must be emitted within one frame (~16ms) of receiving the event to avoid UI freeze perception
Screenshot capture delegated to share service should complete within 2 seconds
security requirements
Screenshot must not include any PII beyond the user's own aggregated stats
Share service must strip metadata (EXIF) from generated images before sharing
ui components
Sharing loading overlay
Share error snackbar or dialog

Execution Context

Execution Tier
Tier 2

Tier 2 - 518 tasks

Can start after Tier 1 completes

Implementation Notes

Use `droppable()` from `bloc_concurrency` to prevent duplicate share dialogs. The transient state can be modelled as a `WrappedSummarySharing` sealed variant, or as a boolean flag `isSharing` on WrappedSummaryLoaded — the sealed variant is cleaner for exhaustive UI matching. Save a reference to the current loaded state before emitting Sharing so you can restore it: `final current = state as WrappedSummaryLoaded; emit(WrappedSummarySharing()); try { await _shareService.share(current.slides[event.slideIndex]); emit(current); } catch (e) { emit(WrappedSummaryShareError(...)); }`. The share service abstraction keeps the BLoC testable without mocking Flutter's rendering pipeline.

Testing Requirements

Unit tests with bloc_test. Test cases: (1) emits [Sharing, Loaded] on successful share, (2) emits [Sharing, ShareError] when share service throws, (3) second ShareSlide while sharing is in progress is dropped (droppable transformer), (4) event ignored when not in Loaded state, (5) BLoC returns to correct Loaded state (same slides, same index) after both success and error paths. Mock the summary share service. Verify that no rendering or image logic is executed inside the BLoC handler itself.

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.