high priority medium complexity frontend pending frontend specialist Tier 6

Acceptance Criteria

Every bar in MonthlyActivityBarChart has a Semantics widget with label in the format '{Month}: {count} activities' (e.g., 'January: 14 activities')
Every segment in ActivityTypeDonutChart has a Semantics widget with label in the format '{Activity type}: {count}, {percentage}%' (e.g., 'Group session: 8, 42%')
All interactive elements (buttons, toggles, filter chips, retry buttons) have a minimum touch target of 44x44 logical pixels, verified programmatically
All text elements pass WCAG 2.2 AA contrast ratio: ≥4.5:1 for normal text and ≥3:1 for large text (≥18pt or ≥14pt bold) against their background
All interactive elements pass WCAG 2.2 AA contrast ratio: ≥3:1 for UI component boundaries against adjacent background
Purely decorative elements (background shapes, dividers) are wrapped in ExcludeSemantics
Groups of related information (e.g., a stat card with value and label) are wrapped in MergeSemantics so screen readers announce them as a single unit
Screen reader traversal order follows the visual reading order (top-to-bottom, left-to-right) across all dashboard sections
No semantic node has an empty or generic label (e.g., 'Button', 'Image') — all labels are descriptive
flutter_test AccessibilityGuideline checks (androidTapTargetGuideline, iOSTapTargetGuideline, labeledTapTargetGuideline, textContrastGuideline) all pass without exceptions

Technical Requirements

frameworks
Flutter
performance requirements
Adding Semantics widgets must not measurably increase frame render time — Semantics nodes are lightweight and this should have no perceptible performance impact
ui components
Semantics widgets on all chart bars and donut segments
MergeSemantics on stat card composite nodes
ExcludeSemantics on decorative elements
SemanticsLabel dynamic string builders for chart data points
Minimum touch target enforcement via SizedBox or Padding wrappers on small interactive elements

Execution Context

Execution Tier
Tier 6

Tier 6 - 158 tasks

Can start after Tier 5 completes

Implementation Notes

Use the Flutter Accessibility Inspector (available in DevTools) and the tester.getSemantics() API to inspect the full semantics tree during development rather than discovering issues only during manual audits. For charts: if using a custom CustomPainter for bars/segments, each data point must be backed by a Semantics widget overlaid on the canvas area — CustomPainter itself cannot emit semantic nodes, so use a Stack with positioned Semantics children over the canvas. For touch targets: prefer wrapping small widgets in a SizedBox with minWidth/minHeight or using InkWell's customBorder with explicit hitTestBehavior rather than padding hacks, to avoid unexpected tap area overflow into adjacent elements. Document all contrast ratio values for design token colors in a comment block in the relevant token file to make future token changes safe.

Run the AccessibilityGuideline tests as part of the standard CI widget test suite, not as a separate manual step.

Testing Requirements

Write flutter_test accessibility tests using the built-in AccessibilityGuideline framework: (1) expectLater(tester, meetsGuideline(androidTapTargetGuideline)) for all interactive elements, (2) expectLater(tester, meetsGuideline(iOSTapTargetGuideline)), (3) expectLater(tester, meetsGuideline(textContrastGuideline)) for all rendered text, (4) expectLater(tester, meetsGuideline(labeledTapTargetGuideline)) for all tappable elements, (5) manually verify Semantics tree structure via tester.getSemantics() and confirm chart data point nodes have correct labels, (6) verify no empty-label semantic nodes in the tree. Additionally, perform a manual audit using VoiceOver (iOS) and TalkBack (Android) in a physical device session — document any traversal order issues found.

Epic Risks (4)
medium impact medium prob technical

PeerMentorStatsList must handle rosters of 40+ mentors efficiently. A naive ListView implementation that re-renders all rows on filter change may cause frame drops on mid-range devices, degrading the experience for coordinators managing large chapters.

Mitigation & Contingency

Mitigation: Use ListView.builder with const constructors for row widgets. Profile the list with Flutter DevTools on a release build against a 60-mentor dataset before submitting for review. Implement sort in the BLoC layer, not in the widget.

Contingency: If frame drops persist, introduce pagination (load 20 mentors, scroll to load more) and add a search filter to reduce visible row count in practice.

medium impact low prob integration

The ActivityTypeDonutChart must render org-configured activity type labels from the org-labels system. If the terminology provider is not available or returns stale labels, chart segments will display raw key strings instead of human-readable organisation-specific names, confusing coordinators.

Mitigation & Contingency

Mitigation: Always resolve labels through the OrganizationLabelsProvider before passing data to the donut chart widget. Implement a fallback that formats the raw key as a readable string (e.g., 'peer_support' → 'Peer Support') if the provider returns null.

Contingency: If the org-labels system is unavailable, display the formatted fallback label and log a warning. Do not block chart rendering on label resolution — render with fallbacks immediately.

high impact high prob technical

fl_chart widgets do not natively expose semantic labels for individual bars and donut segments. Without explicit Semantics wrappers, VoiceOver and TalkBack users will receive no meaningful chart information, failing accessibility requirements critical for Blindeforbundet and NHF deployments.

Mitigation & Contingency

Mitigation: Wrap each fl_chart widget in a Semantics widget with a descriptive label summarising the chart data. Implement the data table fallback toggle from the start, not as an afterthought. Validate with VoiceOver on iOS and TalkBack on Android during development.

Contingency: If fl_chart's rendering pipeline prevents semantic overlay from working reliably, replace chart widgets with a custom Canvas-based implementation that has full semantic control, or use a different charting library with better accessibility support.

medium impact medium prob technical

The period filter state must be preserved when navigating away from the dashboard (e.g., drilling into a mentor's detail screen and pressing back). If the BLoC is re-created on navigation, the filter resets to default, forcing coordinators to re-select their period every time they drill down, degrading operational workflow efficiency.

Mitigation & Contingency

Mitigation: Provide the StatsBloc at a route level above the dashboard screen using a BlocProvider scoped to the statistics navigation shell, not inside the screen widget itself. Verify persistence with a widget integration test that navigates away and back.

Contingency: If route-level scoping is not achievable within the current navigation architecture, persist the last-used filter to a local session store and restore it on screen re-entry.