high priority medium complexity frontend pending frontend specialist Tier 5

Acceptance Criteria

The scope toggle (e.g., 'My chapter / All chapters') is rendered in the dashboard header only when RoleAccessValidator.canViewOrgWideStats(currentUser) returns true
Users with coordinator role only see their own chapter's data — the toggle is not rendered for them
Tapping the toggle dispatches a StatsScopeChangedEvent to the BLoC with the new scope value (coordinator or orgAdmin)
On scope change, the BLoC clears all cached stats data before issuing a new fetch — stale data from the previous scope is never shown alongside new scope data
All dashboard sections transition to loading skeleton state immediately on scope change
After the fetch completes, all sections re-render with data scoped to the selected scope
The toggle's current selection is driven by BLoC state, not local widget state, ensuring it survives hot-reload and state restoration
Toggle Semantics node has a descriptive label (e.g., 'View scope: My chapter') and a hint (e.g., 'Double tap to switch to All chapters') that updates to reflect the current and alternative state
Toggle meets the 44x44pt minimum touch target requirement
Switching scope resets any active period or activity-type filters to their default values to avoid confusing cross-scope filter combinations

Technical Requirements

frameworks
Flutter
BLoC
apis
Supabase REST API — scoped stats queries with chapter_id filter (coordinator) or no chapter filter (org-admin)
data models
StatsScope (enum: coordinator, orgAdmin)
UserRole
CoordinatorStatsState
performance requirements
Scope switch must clear cached BLoC state and begin fetch within one frame
Org-wide stats query (all chapters) may return significantly more data — paginate or aggregate server-side to keep response under 200KB
security requirements
Org-admin scope queries must be enforced server-side via Supabase Row Level Security (RLS) policies — client-side scope toggling alone is not sufficient as a security boundary
The client must not receive data for chapters outside the user's authorised scope even if the scope parameter is manipulated — RLS is the enforcement layer
ui components
ScopeSwitchToggle — segmented control or toggle chip with 'My chapter' / 'All chapters' labels
Semantics widget with dynamic label and hint based on current scope
RoleAccessValidator.canViewOrgWideStats() gate in the dashboard screen build method

Execution Context

Execution Tier
Tier 5

Tier 5 - 253 tasks

Can start after Tier 4 completes

Implementation Notes

Store the current scope in the BLoC state, not in a local StatefulWidget. When processing StatsScopeChangedEvent, emit a CoordinatorStatsLoadingState immediately (clearing all previous data) then fire the scoped repository call. The repository method signature should accept a StatsScope parameter and construct the appropriate Supabase query: for coordinator scope, add a .eq('chapter_id', currentUser.chapterId) filter; for orgAdmin scope, omit the chapter filter and rely on RLS to allow the broader query. Avoid passing the raw role string to the query — use the strongly-typed StatsScope enum at the BLoC and repository boundaries.

RoleAccessValidator should be injected into the screen or BLoC (not accessed via a global singleton) to keep the component testable.

Testing Requirements

Write flutter_test widget tests and BLoC unit tests: (1) verify toggle is not rendered when user has coordinator role only, (2) verify toggle is rendered when user has org-admin role, (3) tap toggle and verify StatsScopeChangedEvent dispatched with correct scope, (4) verify BLoC clears cached data on scope change (state transitions through loading), (5) verify all sections re-render with scope-appropriate data, (6) verify Semantics label and hint update on scope change, (7) verify filters reset to defaults on scope change. Security test: verify Supabase RLS rejects org-wide queries for coordinator-role sessions (integration test against Supabase test environment).

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.