critical priority medium complexity frontend pending frontend specialist Tier 4

Acceptance Criteria

PeerMentorStatsScreen renders a page header with the text 'My Statistics' (or org-label equivalent) and a Semantics(header: true) label
TimeWindowSelector widget appears below the header and defaults to the current month on first load
StatsSummaryCards displays totalSessions, totalHours, and uniqueContacts for the authenticated peer mentor
ActivityChartWidget renders the peer mentor's own activity distribution for the selected time window
Changing the TimeWindowSelector value triggers an async reload — all three child widgets show a loading indicator during fetch and update simultaneously on completion
Loading state: StatsSummaryCards and ActivityChartWidget both show skeleton/shimmer while data is fetching
Error state: a full-screen error card with retry button appears; partial data is NOT shown alongside an error
Data is scoped strictly to the authenticated user's userId — confirmed by provider parameter inspection in tests
Screen is wrapped in a single ConsumerWidget or ConsumerStatefulWidget — no ref.read in build()
Scaffold uses the shared AppScaffold/PageHeader component (no custom AppBar)
All interactive elements meet 44dp minimum tap target
Screen passes accessibility audit: heading landmark, logical focus order, no colour-only information

Technical Requirements

frameworks
Flutter
Riverpod
apis
Supabase REST — peer mentor stats query filtered by userId and date range
data models
PeerMentorStatsData
TimeWindow
StatsSummary
ActivityChartData
performance requirements
Single provider refresh on time window change — no multiple parallel fetches
Screen first meaningful paint under 300ms on cached data
security requirements
userId sourced only from authenticated session (Supabase auth.currentUser.id) — never from route params
Provider must throw AuthException if session is null rather than silently querying with null userId
ui components
AppScaffold / PageHeader with Semantics(header: true)
TimeWindowSelector (task-002)
StatsSummaryCards (task-003)
ActivityChartWidget (task-006)
Full-screen ErrorCard with retry
Skeleton overlays for cards and chart

Execution Context

Execution Tier
Tier 4

Tier 4 - 323 tasks

Can start after Tier 3 completes

Implementation Notes

Create a peerMentorStatsProvider(TimeWindow window) family provider backed by AsyncNotifier. The provider constructor must receive the TimeWindow as a family parameter — do not store the window in a separate StateProvider and watch it inside the notifier, as this creates watch-chain complexity. Instead, when TimeWindowSelector changes, navigate to the new provider family member: ref.watch(peerMentorStatsProvider(selectedWindow)). Use a local selectedWindow StateProvider in the screen widget to hold the current selection and pass it to the family.

Ensure the Semantics heading is applied with Semantics(header: true, child: Text('My Statistics')) — not just a large font size — so VoiceOver/TalkBack announce it as a heading.

Testing Requirements

Widget tests with ProviderScope overrides. Test cases: (1) default time window is current month; (2) changing time window fires provider.refresh() once; (3) all three child widgets update after time window change — verify keys or finder counts; (4) loading state shows skeletons in both cards and chart areas simultaneously; (5) error state hides data and shows retry; (6) userId passed to provider equals mocked auth session userId, not a hardcoded string. Integration test (optional): verify Supabase query includes correct userId filter using a local mock.

Component
Peer Mentor Stats Screen
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.