critical priority medium complexity frontend pending frontend specialist Tier 3

Acceptance Criteria

NavigationBar renders exactly five destinations: Home, Contacts, Add, Work, Notifications in the specified order
Each tab has a Semantics label following the pattern '{Tab Name}, tab, {index} of 5' (e.g., 'Home, tab, 1 of 5')
The selected tab has Semantics(selected: true); all others have Semantics(selected: false)
VoiceOver and TalkBack announce 'selected' for the active tab and not for inactive tabs
Tapping a tab updates TabStateManager via Riverpod and triggers GoRouter navigation to the correct shell branch
The Notifications tab displays a numeric badge when unread count > 0, and the badge count is included in the accessibility label (e.g., 'Notifications, tab, 5 of 5, 3 unread')
When unread count is 0 the badge is not rendered and the label does not mention unread notifications
The Add tab (center) renders a visually distinct floating-action-button-style icon and opens the action menu modal rather than switching shell branches
The bottom nav bar height respects the device safe area inset (MediaQuery.of(context).padding.bottom)
The widget uses only design tokens (colors, typography, spacing) from the app's token system — no hard-coded hex values
All five tabs meet the 48Ă—48 dp minimum touch target requirement

Technical Requirements

frameworks
Flutter
Riverpod
GoRouter
apis
NavigationBar (material3)
NavigationDestination
Semantics widget
Badge widget (Flutter material)
Riverpod (watch TabStateManager provider)
GoRouter StatefulShellRoute.indexedStack
GoRouter.of(context).go()
data models
TabState (current index, notification count)
NotificationCount (unread badge value)
performance requirements
Tab state changes must update the NavigationBar within one frame (no intermediate loading state)
Notification badge count must be sourced from a Riverpod stream provider to update in real time without full widget rebuild
security requirements
Notification badge count must not expose sensitive message content in the accessibility label — only the count
ui components
AccessibleBottomNavBar (new widget)
NavigationBar
NavigationDestination
Badge
Semantics
Design token–based Icon and Label components

Execution Context

Execution Tier
Tier 3

Tier 3 - 413 tasks

Can start after Tier 2 completes

Implementation Notes

Use Flutter's material3 NavigationBar rather than a custom bottom bar to inherit platform-level accessibility handling. Wrap each NavigationDestination's label in an ExcludeSemantics widget and provide a custom Semantics ancestor with the full positional label string to prevent double-announcement. For the Add tab, override onTap to open the action menu modal via Navigator.push (not GoRouter tab switch) and prevent the NavigationBar's selectedIndex from updating. The notification badge: read unread count from a Riverpod StreamProvider backed by a Supabase realtime subscription on the notifications table filtered by the current user.

Use Flutter's built-in Badge widget overlaid on the Notifications icon. Design token usage: reference AppColors.navActive, AppColors.navInactive, AppSpacing.navBarHeight from the token system. Ensure the bar background uses AppColors.surface with appropriate elevation per the design system.

Testing Requirements

Write widget tests for: (1) correct number of NavigationDestination children, (2) selected state matches TabStateManager index, (3) tapping each tab updates Riverpod state and calls GoRouter.go() with correct path, (4) notification badge renders when count > 0 and is absent when count == 0, (5) semantics tree contains correct labels and selected flags using tester.getSemantics(). Write a golden test capturing the bar in selected states for all five tabs. Manually verify TalkBack and VoiceOver announcements on device/simulator and document results. Run AccessibilityGuideline checks in widget tests.

Component
Accessible Bottom Navigation Bar
ui 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.