critical priority medium complexity infrastructure pending infrastructure specialist Tier 4

Acceptance Criteria

GoRouter is configured with a redirect callback that invokes RoleRouteGuard on every navigation event
RoleRouteGuard reads the current active role from the Role State Manager (BLoC/Riverpod) synchronously within the redirect callback
Each GoRoute definition includes a meta/extra field or is referenced in a route permission map specifying the minimum required role(s)
If the active role is NOT in the permitted roles for the target route, the guard returns the appropriate fallback route path
Global admin role is always redirected to '/no-access' regardless of target route
Unauthenticated users are redirected to the login route ('/login') by the guard
Permitted roles access their target route without redirect (guard returns null)
Guard fires on: standard navigation, deep link navigation, app resume from background
Guard is registered once in the GoRouter configuration — not duplicated across routes
Route permission map is defined as a static const (not scattered across route definitions)

Technical Requirements

frameworks
Flutter
GoRouter
BLoC
Riverpod
apis
Permission Checker Service (established in task-005)
data models
ActiveRoleState
RoutePermissionMap
AppRole enum
performance requirements
Redirect callback must be synchronous — no async/await inside the redirect function (use cached role state only)
Guard evaluation must complete within the same frame as navigation intent
security requirements
Role validation must use server-confirmed role state from the auth session — not user-supplied parameters
Redirect logic must be server-side validated as a second layer; client guard is UX-only defense
Deep link parameters must not be used to bypass role checks
ui components
No-Access Screen route ('/no-access')
GoRouter redirect callback wiring

Execution Context

Execution Tier
Tier 4

Tier 4 - 323 tasks

Can start after Tier 3 completes

Implementation Notes

GoRouter's redirect callback receives GoRouterState but cannot be async. Read active role from a synchronously accessible source — a Riverpod provider ref.read() or a BLoC's current state property. Define RoutePermissionMap as a static const Map> keyed by route path pattern. Use GoRouter.routerDelegate.addListener() or refreshListenable (a ChangeNotifier wrapping the role state stream) to re-evaluate the guard whenever role state changes.

This is critical for role switches triggering guard re-evaluation without explicit navigation. Avoid putting business logic in the redirect callback — delegate to RoleRouteGuard.evaluate(route, role) which is pure and testable. The guard should treat an unrecognized/null role the same as unauthenticated.

Testing Requirements

Unit tests for RoleRouteGuard redirect logic: (1) given global_admin role + any route → returns '/no-access', (2) given unauthenticated state + protected route → returns '/login', (3) given coordinator role + coordinator-permitted route → returns null, (4) given peer_mentor role + coordinator-only route → returns fallback route, (5) given org_admin role + permitted route → returns null. Integration test: configure GoRouter with guard, navigate to restricted route with wrong role and assert redirect. Test deep link scenario: simulate app open with deep link to restricted route and assert guard fires. Target 100% branch coverage on redirect logic.

Component
Role-Based Route Guard
infrastructure 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.