Implement Riverpod TabStateManager with in-memory snapshots
epic-navigation-and-gesture-accessibility-foundation-task-008 — Implement the TabStateManager as a Riverpod StateNotifier (or AsyncNotifier) that maintains in-memory NavigationStackSnapshots for all five tab branches. Expose: activeTabIndex (int), setActiveTab(int), updateStackSnapshot(tabIndex, snapshot), getSnapshot(tabIndex). On initialization, load persisted state from NavigationStateRepository and hydrate in-memory snapshots. On every mutation, persist the updated state back to the repository asynchronously.
Acceptance Criteria
Technical Requirements
Execution Context
Tier 2 - 518 tasks
Can start after Tier 1 completes
Implementation Notes
Prefer `AsyncNotifier
For the debounce pattern, use a `Timer? _debounceTimer` field in the notifier — cancel and restart on each mutation, flush on dispose. The `NavigationStateRepository` interface should be injected via a Riverpod `Provider` that is overridden in tests with a `FakeNavigationStateRepository`. Avoid calling `GoRouter` from the notifier — the notifier only manages state; GoRouter observes the notifier via a listener in the shell widget.
This separation is critical for testability.
Testing Requirements
Unit tests in `test/navigation/tab_state_manager_test.dart` using `ProviderContainer`: (1) Initialize with a mock repository returning pre-seeded snapshots — assert `state.snapshots` matches. (2) Call `setActiveTab(2)` — assert `state.activeTabIndex == 2` and repository write was called. (3) Call `setActiveTab(5)` — assert `RangeError` is thrown. (4) Call `updateStackSnapshot(1, mockSnapshot)` — assert `state.snapshots[1] == mockSnapshot`.
(5) Call `getSnapshot(3)` — assert returns snapshot at index 3. (6) Simulate repository write failure — assert state was still updated in-memory and error was logged. (7) Rapid-fire 10 `setActiveTab` calls — assert repository write called at most twice (debounce). Target: 90%+ line coverage on notifier class.
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.
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.