high priority medium complexity frontend pending frontend specialist Tier 1

Acceptance Criteria

RoleSwitchWidget renders only when the authenticated user holds 2 or more roles; single-role users see no widget
Widget displays all roles assigned to the current user with human-readable role labels (e.g. 'Coordinator', 'Peer Mentor', 'Org Admin')
Tapping the widget opens a bottom sheet on mobile form factors listing all available roles
Currently active role is visually distinguished (check mark, bold text, or accent color using design tokens)
Selecting a role closes the bottom sheet and dispatches a RoleChangedEvent to the Role State Manager BLoC/Riverpod provider
Widget is placed in the app bar or home screen header per design spec
All interactive elements have Semantics widgets with descriptive labels (e.g. 'Switch role, currently Coordinator')
Focus is moved to the bottom sheet when opened and returned to the trigger widget when closed (focus management)
Bottom sheet role list items are individually focusable and activatable via keyboard/switch access
Widget passes WCAG 2.2 AA contrast ratio (≥4.5:1) for all text and icon elements
Widget is tested with TalkBack (Android) and VoiceOver (iOS) — all labels are announced correctly
Widget does not render or cause errors when user has exactly one role

Technical Requirements

frameworks
Flutter
BLoC
Riverpod
apis
Supabase auth user_metadata or roles table for fetching user role list
data models
UserRole
AuthenticatedUser
ActiveRoleState
performance requirements
Bottom sheet opens within 150ms of tap
Role list renders without jank for up to 10 roles
security requirements
Role list must be sourced from authenticated session state — never from unvalidated local storage
Do not expose internal role IDs in widget labels; use display names only
ui components
RoleSwitchWidget (StatelessWidget wrapping Semantics + InkWell)
RoleSwitchBottomSheet (DraggableScrollableSheet or showModalBottomSheet)
RoleListTile (ListTile with Semantics, checkmark indicator)
Design token colors for active/inactive role states

Execution Context

Execution Tier
Tier 1

Tier 1 - 540 tasks

Can start after Tier 0 completes

Implementation Notes

Use showModalBottomSheet with isDismissible: true and a SafeArea wrapper. Wrap the trigger element in a Semantics widget with label and hint properties. Use ExcludeSemantics on purely decorative icons. For focus management, use a FocusNode and requestFocus() when the bottom sheet is built.

Source role data from the existing auth BLoC/Riverpod provider established in task-001 — do not make a new Supabase call inside this widget. Use design tokens for all colors and typography (no hardcoded hex values). The widget should be a stateless consumer of the role provider. Consider using a ValueListenableBuilder or context.watch() to reactively show/hide based on role count.

Testing Requirements

Write widget tests using flutter_test: (1) verify widget is absent when user has one role, (2) verify widget is present and shows correct role count label when user has multiple roles, (3) simulate tap and assert bottom sheet appears with all roles listed, (4) assert active role has distinguishing indicator, (5) assert Semantics tree contains correct labels using SemanticsController. Integration test: select a role from the bottom sheet and assert the Role State Manager emits the correct state. Accessibility test: use flutter_test's SemanticsController to verify all interactive elements have non-empty semantic labels. Target 90% branch coverage on widget logic.

Component
Role Switch Widget
ui medium
Epic Risks (3)
high impact medium prob technical

Combining GoRouter's declarative redirect logic in the route guard with StatefulShellRoute's stateful branch management is known to produce subtle bugs where the shell rebuilds unnecessarily on role switches, losing tab state or causing double-navigation events.

Mitigation & Contingency

Mitigation: Implement the route guard as a GoRouter redirect callback that only evaluates role from an already-resolved Riverpod provider (not async). Use a dedicated ShellRoute navigator key per tab branch to anchor state independently of role-driven rebuilds. Write integration tests for the full navigation graph.

Contingency: If StatefulShellRoute state loss is confirmed during QA, fall back to a manual tab state preservation approach using a TabStateManager service that caches the last route per tab and restores it after role switches, decoupling tab state from the shell lifecycle.

medium impact high prob scope

The role-based home screen must render three significantly different layouts (coordinator dashboard, peer mentor activity summary, org admin overview). If these variants are implemented as a single widget with conditionals, the file will become unmaintainable and difficult to test in isolation, especially as each variant grows with downstream feature additions.

Mitigation & Contingency

Mitigation: Design the role-based home screen as a router/dispatcher widget that delegates to three separate variant widgets (CoordinatorHomeView, PeerMentorHomeView, OrgAdminHomeView). Each variant is independently testable and can be developed by separate team members in parallel.

Contingency: If variant coupling has already occurred before this risk is addressed, refactor to the dispatcher pattern in a dedicated cleanup task before feature handoff. The dispatcher pattern is a straightforward extraction that carries low refactoring risk.

medium impact medium prob integration

The no-access screen must link global admin users to the correct admin portal URL, which may differ per organization (NHF, HLF, Blindeforbundet each have their own admin portals). Hardcoding a single URL will result in wrong or broken links for some global admin users.

Mitigation & Contingency

Mitigation: Source the admin portal URL from the organization's configuration record in Supabase rather than hardcoding it. The no-access screen reads the active org context and resolves the portal URL dynamically. Provide a safe fallback to a generic Norse Digital Products support page if the URL is not configured.

Contingency: If dynamic URL resolution is not ready when the no-access screen ships, display a static instruction to contact the organization's administrator along with a support email address as an interim measure, and track the URL configuration task as a follow-up.