critical priority low complexity frontend pending frontend specialist Tier 3

Acceptance Criteria

Every interactive element in SummaryShareOverlay (share button, close button, any secondary actions) has a non-empty Semantics label sourced from SummaryAccessibilityProvider
The preview image region is wrapped in a Semantics widget with a descriptive label (e.g. 'Preview of your annual impact card showing X activities and Y milestones') — label is dynamic and reflects current data
When the overlay opens, focus is programmatically moved to the first interactive element (share button or close button per UX spec) using FocusNode.requestFocus() after the first frame settles
The dismiss gesture (swipe down on bottom sheet, or tap outside) is reachable via screen reader — ExcludeSemantics is NOT applied to the scrim; it has a Semantics label 'Close share overlay'
Reduced-motion flag from SummaryAccessibilityProvider suppresses any entry/exit animation of the overlay (fade or scale transition replaced with instant show/hide)
All Semantics labels pass VoiceOver (iOS) and TalkBack (Android) manual verification — no label reads as the raw widget type (e.g. 'button') without context
Screen reader announcement fires when overlay opens, stating the purpose of the sheet (e.g. 'Share your annual impact card')
No Semantics merge conflicts — confirm with Flutter's accessibility inspector that there are no hidden children being read when they should not be

Technical Requirements

frameworks
Flutter
BLoC (to read SummaryAccessibilityProvider state)
flutter_test (widget tests with SemanticsController)
apis
SummaryAccessibilityProvider.getShareOverlayLabels() — internal API
SummaryAccessibilityProvider.reducedMotionEnabled — bool flag
Flutter Semantics widget
FocusNode / FocusScope for programmatic focus management
SemanticsController (flutter_test) for assertion
data models
AccessibilityLabels (structured label map from SummaryAccessibilityProvider)
SummaryStatCardData (used to build dynamic preview alt-text)
performance requirements
Focus management call must occur after the first post-frame callback — use WidgetsBinding.instance.addPostFrameCallback to avoid calling requestFocus before the widget tree is laid out
security requirements
Semantics labels must not expose PII (e.g. do not read out a user's full name unless it is already visible on screen)
ui components
SummaryShareOverlay
Semantics (wrapper widgets for each interactive element and image preview)
FocusNode (for first-element focus on open)

Execution Context

Execution Tier
Tier 3

Tier 3 - 413 tasks

Can start after Tier 2 completes

Implementation Notes

Consume SummaryAccessibilityProvider via BlocBuilder or a simple InheritedWidget read — do not duplicate label strings in the overlay file. Build dynamic alt-text for the preview by combining stat totals: 'Annual impact card: {activityCount} activities, {milestoneCount} milestones earned'. For the focus management, store the share button's FocusNode as a field on the State class, call requestFocus inside addPostFrameCallback in initState (or didChangeDependencies if data-driven). For reduced-motion, conditionally swap AnimationController durations to Duration.zero or wrap with AnimatedSwitcher that checks the flag.

Avoid using ExcludeSemantics on the scrim — instead give the scrim a GestureDetector + Semantics with an onTap action labelled 'Dismiss'.

Testing Requirements

Write Flutter widget tests using SemanticsController: (1) assert all interactive Semantics labels are non-empty strings; (2) assert preview image Semantics label contains activity count; (3) assert scrim has a dismiss Semantics action; (4) assert focus node receives focus on open. Run flutter analyze --no-fatal-infos to catch missing Semantics. Perform manual VoiceOver smoke test on TestFlight build: navigate entire overlay via swipe-right without touching the screen directly. Target: zero unlabelled interactive elements reported by Accessibility Inspector.

Component
Summary Share Overlay
ui medium
Epic Risks (2)
medium impact medium prob technical

Simultaneous count-up animations across multiple stat cards and chart draw-in animations on lower-end Android devices may cause frame drops below 60fps, degrading the premium Wrapped experience and making the feature feel unpolished.

Mitigation & Contingency

Mitigation: Stagger animation starts using AnimationController with staggered intervals rather than starting all animations simultaneously. Use RepaintBoundary around each animated widget to isolate rasterisation. Profile on a mid-range Android device (e.g., equivalent to Pixel 4a) during development, not just at QA.

Contingency: If frame rate targets cannot be met on low-end devices, implement a device-capability check at startup and substitute simpler fade-in animations for the count-up and chart draw-in on devices below a CPU performance threshold.

medium impact low prob integration

The activity-type-breakdown-widget must render organisation-specific activity type labels sourced from the terminology system. If the terminology provider is not yet integrated at the time this widget is built, the widget will display hardcoded system labels, which is a regression risk for multi-org support.

Mitigation & Contingency

Mitigation: Accept activity type labels as a typed parameter in the widget constructor rather than reading from the terminology provider directly inside the widget. The BLoC or repository layer resolves labels before passing them to the widget, maintaining clean separation and testability.

Contingency: If terminology resolution is unavailable at widget integration time, display internal activity type keys as a temporary fallback with a localised suffix '(label pending)' visible only in non-production builds so QA can identify unresolved labels.