critical priority low complexity frontend pending frontend specialist Tier 3

Acceptance Criteria

Each segment in ActivityTypeBreakdownWidget is wrapped in a Semantics node with label matching exactly: '[org-specific label]: [percentage]%, [absolute count] activities' where the org-specific label comes from OrgLabelsProvider
The word 'activities' in the label uses the organisation-specific term from OrgLabelsProvider (e.g. may be 'sessions', 'visits', or 'activities' depending on org)
All decorative elements within each segment (colour bar, chart arc, percentage text rendered as a visual element) are wrapped with ExcludeSemantics
When a segment is expanded via tap, the newly revealed detail row announces its content to screen readers via SemanticsService.announce or a liveRegion Semantics node
The expand/collapse button behaviour is exposed via Semantics with button:true and a hint string such as 'Double tap to expand details' / 'Double tap to collapse details'
Focus order across segments is left-to-right (or top-to-bottom for stacked bar layout), matching visual order
The entire widget passes WCAG 2.2 AA criterion 1.3.1 and 4.1.2
flutter test --plain-name 'semantics' passes with all semantics assertions green

Technical Requirements

frameworks
Flutter
flutter_test
apis
SummaryAccessibilityProvider (Riverpod provider)
OrgLabelsProvider (Riverpod provider)
Flutter Semantics widget
SemanticsService.announce
data models
ActivityTypeSegment (activityTypeId, label, percentage, absoluteCount)
OrgLabels (activity type terminology map)
performance requirements
Semantics tree update must not add additional frame latency to the expand/collapse interaction
security requirements
Announced strings must not expose raw database IDs or internal system identifiers
ui components
ActivityTypeBreakdownWidget
Semantics (per segment)
ExcludeSemantics (decorative children)
SummaryAccessibilityProvider
OrgLabelsProvider

Execution Context

Execution Tier
Tier 3

Tier 3 - 413 tasks

Can start after Tier 2 completes

Implementation Notes

Construct the announcement string in SummaryAccessibilityProvider as a pure function taking (label: String, percentage: double, count: int, activityTerm: String) → String, so it can be unit-tested independently of the widget. Resolve OrgLabelsProvider for the activity term (e.g. orgLabels.activityNounPlural) before passing it into the Semantics label. Use Semantics(label: announcementString, child: ExcludeSemantics(child: visualSegmentContent)) pattern per segment.

For the expand announcement, use a didUpdateWidget check: when expandedSegmentId changes to a non-null value, call SemanticsService.announce with the detail row content. Mark the GestureDetector or InkWell as Semantics(button:true, hint:...) rather than relying on Flutter's automatic button semantics to avoid platform-specific behaviour differences between VoiceOver and TalkBack. This task is critical because the Blindeforbundet user base relies heavily on screen readers — do not defer or simplify the implementation.

Testing Requirements

Widget tests using flutter_test SemanticsController: (1) for each segment, assert tester.getSemantics returns label matching '[label]: [N]%, [count] [activity_term]'; (2) assert decorative elements have no semantics label; (3) tap a segment and assert SemanticsService.announce was called with a string containing the expanded detail content; (4) mock OrgLabelsProvider to return custom activity term (e.g. 'sessions') and assert the announcement string uses 'sessions' not 'activities'; (5) assert each segment Semantics node has isButton:true with an appropriate hint. Run tester.ensureSemantics() before all semantics tests.

Component
Activity Type Breakdown Widget
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.