critical priority medium complexity infrastructure pending frontend specialist Tier 1

Acceptance Criteria

GoRouter is configured with a StatefulShellRoute containing exactly five named branches: home, contacts, add, work, and notifications
Each branch preserves its own navigation stack independently when switching tabs (StatefulShellRoute behavior confirmed by tab-switch test)
Route guards redirect unauthenticated users to the login screen before any protected branch is accessed
Role-based route guards prevent coordinators, peer mentors, and org admins from accessing routes not applicable to their role
Deep-link paths are defined for all top-level branch routes (e.g., /home, /contacts, /add, /work, /notifications)
Nested routes within each branch (e.g., /contacts/:id, /contacts/:id/edit) are correctly defined and navigable
The GoRouter instance is provided via Riverpod so TabStateManager and other services can react to route changes
Navigator.pop() and context.pop() via GoRouter correctly unwind nested routes within a branch without switching tabs
Root shell routes (the five tab roots) do NOT show the PersistentBackButton — the route config exposes an isShellRoot flag or equivalent
All route names are defined as constants (no magic strings) in a dedicated route_names.dart file
GoRouter redirect logic handles the no-access screen for blocked global-admin roles
Integration test confirms deep-link URL launches the correct branch and screen

Technical Requirements

frameworks
Flutter
go_router (StatefulShellRoute)
Riverpod
data models
UserRole
AuthSession
performance requirements
Tab switch must complete within one frame (16ms) — no rebuilds of sibling branches
Route guard evaluation must be synchronous or complete before first frame render to avoid flash of unauthorized content
security requirements
Route guards must read auth state from Riverpod provider, never from a local variable that could be stale
Role checks must be enforced server-side as well; client-side guards are UX only
Deep-link handling must validate path parameters to prevent navigation injection

Execution Context

Execution Tier
Tier 1

Tier 1 - 540 tasks

Can start after Tier 0 completes

Implementation Notes

Use go_router's StatefulShellRoute with StatefulShellBranch for each of the five tabs; this is the canonical Flutter approach for preserving tab state. Define a single GoRouter instance in a Riverpod Provider so the TabStateManager (task-004) can observe or drive navigation without tight coupling. Place all route path constants in lib/navigation/route_names.dart and all route guard redirect logic in lib/navigation/route_guards.dart. For role-based guards, inject the current user's role from a Riverpod provider inside the redirect callback — avoid async gaps that could cause a frame of unguarded content.

The Add tab (index 2) likely opens a modal action menu rather than a persistent stack; model this as a branch with a single root route that launches bottom sheets, keeping the shell route architecture clean. Ensure the router exposes a way to determine if the current location is a shell root (needed by PersistentBackButton in task-007); a simple helper `bool isShellRoot(String location)` checking against the five root paths suffices.

Testing Requirements

Write widget/integration tests using flutter_test and go_router's testing utilities. Test 1: Verify five branches exist and each has the correct root path. Test 2: Simulate an unauthenticated state and confirm redirect to login for every protected branch. Test 3: Navigate to a nested route (e.g., /contacts/123/edit), then switch tabs and switch back — confirm the nested route is preserved.

Test 4: Trigger a deep-link URL and confirm the correct screen is rendered. Test 5: Confirm global-admin role is redirected to the no-access screen. Aim for 100% coverage of guard logic and route constant definitions.

Component
Navigation Route Configuration
infrastructure medium
Epic Risks (3)
high impact high prob technical

Flutter's ModalBottomSheet and showDialog do not automatically confine VoiceOver or TalkBack focus to the modal's subtree on all platform versions. Background content may remain reachable by screen readers, confusing users and violating WCAG 2.2 criterion 1.3.1.

Mitigation & Contingency

Mitigation: Wrap modal content in an ExcludeSemantics or BlockSemantics widget for background content. Use a Semantics node with liveRegion on the modal container and manually request focus via FocusScope after the modal animation completes. Test on both iOS (VoiceOver) and Android (TalkBack) during widget development.

Contingency: If platform-level focus trapping is unreliable, implement a custom modal wrapper widget that uses a FocusTrap widget (available in Flutter's internal tooling) and an Overlay entry with semantics blocking on the dimmed background layer.

medium impact medium prob technical

On iOS, the system-level swipe-back gesture (UINavigationController) can bypass PopScope and GoRouter's gesture suppression, meaning users can still accidentally dismiss screens via swipe even after the component is implemented. This breaks the gesture-free contract for motor-impaired users.

Mitigation & Contingency

Mitigation: Set popGestureEnabled: false in GoRouter route configurations where swipe-back is suppressed. Test specifically against Flutter's CupertinoPageRoute, which respects this flag, and verify that GoRouter generates Cupertino routes on iOS rather than Material routes with gesture enabled.

Contingency: If go_router's popGestureEnabled flag does not propagate correctly, wrap affected routes in a WillPopScope replacement (PopScope with canPop: false) and file a bug with the go_router maintainers. Document the workaround in the navigation-route-config component for future maintainers.

medium impact medium prob scope

The feature description implies migrating all existing ModalBottomSheet and dialog call sites across the app to use the new accessible helpers, which is a cross-cutting change. Scope underestimation could mean the epic finishes the new components but leaves many call sites un-migrated, leaving the accessibility promise partially broken.

Mitigation & Contingency

Mitigation: Audit all existing modal call sites at the start of the epic (grep for showModalBottomSheet, showDialog, showCupertinoDialog) and add the count to the task list. Treat migration as explicit tasks, not an implied post-step.

Contingency: If migration scope grows beyond the epic's estimate, create a follow-up tech-debt epic scoped only to call-site migration, and gate the release on at minimum all flows used by the accessibility user-story acceptance criteria being migrated.