critical priority medium complexity frontend pending frontend specialist Tier 3

Acceptance Criteria

RoleAwareBottomNav renders only the tabs permitted for the active role per the tab permission matrix
Tab permission matrix is defined as a constant/config (not hardcoded per widget) covering: coordinator, peer_mentor, org_admin roles and the 5 base tabs (Home, Contacts, Add, Work, Notifications)
Coordinator role sees: Home, Contacts, Add, Work, Notifications
Peer Mentor role sees: Home, Contacts, Add, Notifications (Work tab hidden)
Org Admin role sees: Home, Contacts, Work, Notifications (Add tab hidden or restricted)
Tab bar updates immediately when active role changes without Navigator pop or app restart
Each tab maintains its own navigation stack (StatefulShellRoute shell branches) — navigating to another tab and back does not reset the previous tab's stack
Tab icons and labels use design tokens for consistent styling
Active tab is visually distinguished using design token accent color
Bottom nav is accessible: each tab has a Semantics label, selected state is announced by screen readers
No tab is rendered for a role that does not have permission — the tab index does not exist in the widget tree
GoRouter configuration correctly maps each shell branch to its permitted routes

Technical Requirements

frameworks
Flutter
GoRouter (StatefulShellRoute)
BLoC
Riverpod
data models
ActiveRoleState
TabPermissionMatrix
RoleTabConfig
performance requirements
Tab switch must be instantaneous (<16ms frame budget) — no async calls on tab tap
Shell branches must be lazily initialized to avoid unnecessary widget tree inflation
security requirements
Tab visibility is purely a UI concern — route-level authorization is enforced separately by RoleRouteGuard
Tab permission matrix must be sourced from a trusted, server-validated role state — not from user input
ui components
RoleAwareBottomNav (StatefulWidget consuming role state)
NavigationBar or BottomNavigationBar with dynamic item list
Per-tab icon + label using design tokens
StatefulShellRoute shell branch configuration in GoRouter

Execution Context

Execution Tier
Tier 3

Tier 3 - 413 tasks

Can start after Tier 2 completes

Implementation Notes

Define the tab permission matrix as a static const Map> in a dedicated RoleTabPermissions class. Use StatefulShellRoute.indexedStack in GoRouter for tab state preservation. Each shell branch should be a separate StatefulShellBranch with its own Navigator key. When the active role changes, update the selected tab index to 0 (home) if the previously selected tab is no longer permitted for the new role — avoid dangling index.

Use context.watch() or BlocBuilder at the RoleAwareBottomNav level to rebuild only the nav bar (not the entire page) on role change. Avoid placing heavy logic inside the BottomNavigationBar builder — keep it declarative. Reference the existing bottom nav widget pattern from the shared widget library established in prior tasks.

Testing Requirements

Widget tests using flutter_test: (1) render RoleAwareBottomNav with each role and assert correct tab count and labels, (2) simulate role switch and assert tab list updates without Navigator.pop, (3) navigate to tab 2, switch roles, navigate back to equivalent tab and assert state preserved. GoRouter integration tests: (1) assert each shell branch navigates to the correct route, (2) assert disallowed tab routes are not reachable via bottom nav. Accessibility: assert each NavigationDestination has a valid semanticLabel. Test all three role scenarios (coordinator, peer_mentor, org_admin) exhaustively.

Target 90% branch coverage on tab permission matrix logic.

Component
Role-Aware Bottom Navigation
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.