critical priority medium complexity frontend pending frontend specialist Tier 3

Acceptance Criteria

StatefulShellRoute's onNavigationStateChange callback invokes tabStateManager.setActiveTab(index) on every tab transition
tabStateManager.updateStackSnapshot() is called with the current navigator stack after each route push or pop within a shell branch
The bottom navigation bar's selected index is driven exclusively by the activeTabIndex stream — no local setState is used to manage the selected tab indicator
Switching tabs via the bottom nav bar, deep link, or programmatic navigation all result in identical Riverpod state updates
Re-entering the app from background restores the correct tab index from TabStateManager without re-emitting a navigation event
No duplicate state updates occur when the user taps the already-active tab
GoRouter and TabStateManager state remain consistent across hot-restart in debug mode
The wiring layer introduces no direct coupling between GoRouter and persistence logic — all persistence is delegated through TabStateManager

Technical Requirements

frameworks
Flutter
GoRouter (go_router)
Riverpod
apis
StatefulShellRoute.onNavigationStateChange
TabStateManager.setActiveTab
TabStateManager.updateStackSnapshot
StreamProvider / StateNotifierProvider (Riverpod)
data models
TabStateSnapshot
NavigationRouteConfig
performance requirements
Tab switch must complete visual update within one frame (≤16ms on 60Hz devices)
Stream subscription must be cancelled when the bottom nav widget is disposed to prevent memory leaks
No synchronous blocking calls inside the onNavigationStateChange callback
security requirements
No sensitive route parameters or user data should be stored in the tab snapshot beyond route path and stack depth
State exposed via the activeTabIndex stream must not leak role-specific routing information to widgets outside the intended scope
ui components
BottomNavigationBar (or NavigationBar)
StatefulShellRoute shell widget
ConsumerWidget or HookConsumerWidget wrapping the shell

Execution Context

Execution Tier
Tier 3

Tier 3 - 413 tasks

Can start after Tier 2 completes

Implementation Notes

Use ref.listen inside the shell ConsumerStatefulWidget to react to GoRouter's currentConfiguration changes, rather than overriding didChangeDependencies — this avoids double-build issues. The onNavigationStateChange callback in StatefulShellRoute fires before the page animation completes, so updateStackSnapshot should capture the committed route list from the branch navigator's pages list, not from an in-progress transition. Guard against index-out-of-range if GoRouter emits a configuration before TabStateManager is fully hydrated by reading the current state synchronously before subscribing to the stream. Avoid wrapping the entire shell in a rebuild-heavy Consumer; instead, use select() to observe only the integer index so the shell itself does not rebuild when unrelated Riverpod state changes.

Testing Requirements

Unit tests (covered by task-011) verify TabStateManager logic in isolation. Integration tests for this wiring task should use flutter_test with a mock GoRouter and a fake TabStateManager to assert that every navigation event triggers the correct manager calls. Specifically: (1) tap tab 2 → setActiveTab(2) called once, (2) push sub-route on tab 2 → updateStackSnapshot called with correct stack, (3) pop back → updateStackSnapshot updated, (4) navigate to same tab → no duplicate setActiveTab call. Widget tests should pump the shell scaffold and verify the bottom nav selectedIndex matches the stream value without any setState.

Component
Tab State Manager
service medium
Epic Risks (2)
high impact medium prob technical

StatefulShellRoute branch navigator state can interact unexpectedly with GoRouter's imperative navigation (go, push, replace), causing state snapshots to desync from actual route stacks. This could manifest as a user returning to a tab and seeing a different screen than expected, breaking the core motor-fatigue promise.

Mitigation & Contingency

Mitigation: Write integration tests that simulate cross-tab navigation with nested pushes before any UI layer is built. Pin go_router to a tested minor version and review the StatefulShellRoute changelog before upgrading.

Contingency: If branch navigator state consistently desyncs, fall back to a manual stack snapshot strategy using a custom NavigatorObserver that records and replays navigation events independently of StatefulShellRoute internals.

medium impact medium prob technical

Persisted navigation stacks in shared_preferences can become stale or corrupt if route paths are renamed during development, causing app crashes or infinite redirect loops on cold start for users who have an old snapshot.

Mitigation & Contingency

Mitigation: Version the persisted schema with a format key. On app start, validate that all stored route paths exist in the current route config before restoring; silently discard invalid entries rather than crashing.

Contingency: Implement a safe-mode cold start that skips state restoration after a detected crash (via a dirty-launch flag written at startup and cleared on successful first frame), falling back to the default root tab.