critical priority low complexity frontend pending frontend specialist Tier 3

Acceptance Criteria

The shared AppBar/page header wrapper sets automaticallyImplyLeading: false on all non-root screens so the platform default back button never appears
PersistentBackButton occupies the leading slot of the AppBar on every secondary route (depth > 0 in GoRouter)
PersistentBackButton is not rendered on root-level tabs (Home, Contacts, Add, Work, Notifications) where the bottom nav is the primary navigation
Tapping the back button triggers GoRouter.of(context).pop() and navigates to the correct previous screen
The button meets a minimum touch target of 48×48 dp as required by WCAG 2.2 target size criteria
The button is announced by TalkBack and VoiceOver with a label such as 'Back' or the localized equivalent
The button remains visible and functional regardless of whether the device supports back-swipe gestures (iOS edge swipe, Android predictive back)
No visual regression on screens that previously relied on the platform leading widget
Integration tests confirm button presence on a minimum of three distinct secondary route types (detail screen, wizard step, settings sub-page)

Technical Requirements

frameworks
Flutter
GoRouter
apis
GoRouter.of(context).pop()
GoRouter.of(context).canPop()
SemanticsService
performance requirements
AppBar rebuild must not trigger full subtree rebuild; use const constructor where possible
Route depth check (canPop) must be resolved synchronously to avoid button flash on route push
security requirements
Back navigation must not expose protected screens to unauthenticated users; verify GoRouter redirect guards still apply after pop
ui components
PersistentBackButton (from task-007)
Shared AppBar wrapper widget
AppBar (Flutter material)
IconButton with Semantics label

Execution Context

Execution Tier
Tier 3

Tier 3 - 413 tasks

Can start after Tier 2 completes

Implementation Notes

In the shared page header widget (likely PageHeader or AppScaffold), add a leading parameter that defaults to PersistentBackButton when context.canPop() is true and the current route is not a root shell branch. Pass automaticallyImplyLeading: false unconditionally to AppBar. Use GoRouter's routerDelegate or RouteMatchList to detect root vs secondary routes rather than checking Navigator.canPop() directly, since GoRouter's shell routes can interfere with the standard Navigator stack. Ensure the back button icon uses the app's design token icon set (not the raw Icons.arrow_back) so it respects theme changes.

On iOS, retain WillPopScope or PopScope wrapping at the page level to handle swipe-back consistently with button-back behavior.

Testing Requirements

Write widget tests verifying: (1) AppBar renders PersistentBackButton in leading slot on a secondary route, (2) automaticallyImplyLeading is false, (3) tapping the button calls GoRouter.pop(), (4) button is absent on root tab routes. Write an integration test using flutter_test + GoRouter test harness that navigates to a detail screen and confirms back button presence and correct pop behavior. Verify semantics tree contains a labeled back button node using tester.getSemantics().

Component
Persistent Back Button
ui low
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.