high priority medium complexity frontend pending frontend specialist Tier 3

Acceptance Criteria

Tapping a bar rod in the BarChart displays a tooltip overlay with the date label, session count, and total hours for that X position
Tapping a data point on the LineChart displays a tooltip overlay with the same information
Tooltip is rendered as an overlay (not inside fl_chart's built-in tooltip mechanism) so it can be dismissed independently
Tapping anywhere outside the tooltip (or on the same bar again) dismisses the tooltip
Tooltip text color has ≥4.5:1 contrast ratio against the tooltip background color
Tooltip uses design token colors and typography — no hardcoded values
Each bar rod and line point is wrapped with a Semantics widget providing: label '[date label]: [session count] sessions, [hours] hours', button: true
The overall chart region has a Semantics container label: 'Activity chart, [granularity] view'
Tooltip can be dismissed by pressing the Escape key (keyboard users) and via the Flutter SemanticsAction.dismiss (switch access / TalkBack)
Tooltip does not occlude the granularity toggle row at the top of the chart widget
When no bar/point is selected, no tooltip is visible and there is no residual overlay entry in the Overlay stack
Tooltip dismissal correctly cleans up the OverlayEntry to prevent memory leaks

Technical Requirements

frameworks
Flutter
fl_chart
flutter_test
data models
ActivityChartData
ChartDataPoint
ChartSeries
performance requirements
Tooltip overlay insertion and removal complete within one frame (16ms)
No Overlay stack leaks — each OverlayEntry must be removed before a new one is inserted
security requirements
Tooltip content is aggregate data only — no PII exposed
ui components
OverlayEntry (Flutter built-in) for tooltip rendering
Semantics (Flutter built-in) for chart accessibility
Focus (Flutter built-in) for keyboard dismiss handling
Design token color and typography constants

Execution Context

Execution Tier
Tier 3

Tier 3 - 413 tasks

Can start after Tier 2 completes

Implementation Notes

Use fl_chart's BarTouchData.barTouchCallback and LineTouchData.touchCallback to receive tap events, then insert a custom OverlayEntry positioned relative to the tapped bar/point. Compute the tooltip screen position using RenderBox.localToGlobal on the chart RenderBox plus the bar's x offset. Store the active OverlayEntry in a StatefulWidget state field (_tooltipEntry). Always call _tooltipEntry?.remove() before inserting a new entry.

For keyboard dismiss, wrap the chart in a Focus widget and intercept LogicalKeyboardKey.escape in onKeyEvent. For Semantics wrapping of individual bars: fl_chart does not natively support per-bar Semantics; use a Stack with invisible SizedBox Semantics widgets positioned over each bar's pixel region — compute positions from the chart's axis scale after layout (use LayoutBuilder + post-frame callback). Place all tooltip logic in a TooltipController mixin or private helper class to keep ActivityChartWidget readable.

Testing Requirements

Widget tests in test/stats/activity_chart_tooltip_test.dart. Required scenarios: (1) tapping a bar triggers tooltip visibility (find tooltip widget in tree); (2) tapping outside tooltip dismisses it (tooltip widget absent from tree); (3) tapping same bar again dismisses tooltip; (4) Semantics tree for each bar contains correct label with date, session count, and hours; (5) overall chart region has Semantics container label; (6) after tooltip dismissal, OverlayEntry is not present (verify via debugDumpLayerTree or overlay child count); (7) SemanticsAction.dismiss on tooltip removes it from the Overlay. Accessibility test: use flutter_test SemanticsHandle and expect(tester.getSemantics(find.byType(ActivityChartWidget)), matchesSemantics(...)) for the container label.

Component
Activity Chart Widget
ui medium
Epic Risks (4)
high impact high prob technical

fl_chart renders chart elements on a Canvas, making individual bars and data points invisible to the Flutter Semantics tree by default. Without explicit Semantics wrappers, VoiceOver and TalkBack users receive no chart information, violating the WCAG 2.2 AA requirement mandated by all three partner organizations.

Mitigation & Contingency

Mitigation: Wrap the fl_chart widget in a Semantics node with a dynamically generated textual description of the chart data (e.g., 'Bar chart: January 12, February 8, March 15 sessions'). Implement a collapsible data table alternative beneath the chart that screen readers can navigate row by row. Validate with VoiceOver on iOS and TalkBack on Android before the epic is marked complete.

Contingency: If fl_chart's Canvas rendering cannot be made accessible within the epic timeline, ship the chart hidden from the Semantics tree with ExcludeSemantics and promote the data table alternative to first-class UI so screen reader users have full access to the information. Log a tech-debt item to revisit native chart accessibility in a future sprint.

medium impact medium prob scope

Coordinators managing up to 5 chapters (NHF requirement) require the PeerMentorStatsList to display chapter affiliation labels for each row. With large chapter lists and many peer mentors, the list could become overwhelming and cause layout overflow or scrolling performance issues on lower-end Android devices.

Mitigation & Contingency

Mitigation: Implement chapter filtering as a segmented control above the list so coordinators can scope the list to one chapter at a time. Use ListView.builder (lazy rendering) rather than a Column of all rows. Profile scroll performance on a low-end Android device (Pixel 4a equivalent) with 50 peer mentors in scope during development.

Contingency: If multi-chapter display causes unacceptable performance, ship with single-chapter scope as the default view and a chapter switcher dropdown, deferring the combined cross-chapter list to a follow-up sprint.

low impact low prob technical

Summary cards and the chart widget rebuilding simultaneously on provider state change could cause a visible jank frame on slower devices, degrading perceived quality especially since this screen is intended to feel motivating and polished for gamification purposes.

Mitigation & Contingency

Mitigation: Use AnimatedSwitcher with a short fade transition (150ms) on the stats cards and chart so that data replacement feels intentional rather than jarring. Profile with Flutter DevTools on a mid-range device and ensure no frame exceeds 16ms during a time-window switch.

Contingency: If animation introduces complexity that delays delivery, ship without animation and use a loading skeleton (shimmer effect) during re-fetch instead, which is simpler and equally effective at masking the data swap.

high impact low prob security

If the role-based screen dispatch is misconfigured, a peer mentor could navigate to the coordinator stats screen and see aggregated chapter data for all peer mentors, which is a data privacy violation and a compliance risk for all three organizations.

Mitigation & Contingency

Mitigation: Implement role guard at the router level using an existing role-route-guard component so the coordinator screen route is unreachable for peer mentor roles. Add a widget test that mounts the coordinator screen with a peer mentor session token and asserts that the guard redirects to the no-access screen.

Contingency: If a bypass is found in QA, add a secondary in-screen role assertion in the coordinator screen's initState that throws an AuthorizationException and navigates to the error screen, ensuring defence in depth regardless of router configuration.