# 前端导航解耦与统一缓存重构 Implementation Plan > **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task. **Goal:** 完成导航分级回退(一级唯一 Home)与统一缓存改造,实现本地优先显示、后台静默刷新、写后精准失效,并落地“提醒取消即实时归档 + App 关闭时延迟归档兜底”。 **Architecture:** 路由层采用“一级唯一 Home + 二级业务页 + 三级细节页”的分级返回模型,二级页面返回统一回 Home,退出入口仅 Home;数据层新增 `core/cache` 统一缓存模块(memory + persistent + hybrid);业务层通过 repository 接入缓存策略与失效器。提醒动作采用实时归档优先,pending outbox 仅用于 App 不可用场景兜底。 **Tech Stack:** Flutter, go_router, get_it, shared_preferences, flutter_test, mocktail --- ### Task 0: 锁定导航分级与退出语义(一级/二级/三级) **Files:** - Modify: `apps/lib/core/router/app_router.dart` - Modify: `apps/lib/features/home/ui/navigation/home_return_policy.dart` - Modify: `apps/lib/features/calendar/ui/screens/calendar_dayweek_screen.dart` - Modify: `apps/lib/features/calendar/ui/screens/calendar_month_screen.dart` - Modify: `apps/lib/features/todo/ui/screens/todo_quadrants_screen.dart` - Modify: `apps/lib/features/settings/ui/screens/settings_screen.dart` - Test: `apps/test/features/home/ui/navigation/home_return_policy_test.dart` **Step 1: Write the failing test** ```dart test('second-level pages should return to home instead of exiting app', () { final action = resolveHomeReturnAction( canPop: false, isAuthEntry: false, forceGoHome: true, ); expect(action, HomeReturnAction.goHome); }); ``` **Step 2: Run test to verify it fails** Run: `cd apps && flutter test test/features/home/ui/navigation/home_return_policy_test.dart` Expected: FAIL with old return behavior. **Step 3: Write minimal implementation** ```dart if (forceGoHome) return HomeReturnAction.goHome; ``` **Step 4: Run tests to verify they pass** Run: `cd apps && flutter test test/features/home/ui/navigation/home_return_policy_test.dart` Expected: PASS. **Step 5: Commit** ```bash git add apps/lib/core/router/app_router.dart apps/lib/features/home/ui/navigation/home_return_policy.dart apps/lib/features/calendar/ui/screens/calendar_dayweek_screen.dart apps/lib/features/calendar/ui/screens/calendar_month_screen.dart apps/lib/features/todo/ui/screens/todo_quadrants_screen.dart apps/lib/features/settings/ui/screens/settings_screen.dart apps/test/features/home/ui/navigation/home_return_policy_test.dart git commit -m "feat: enforce hierarchical back navigation and home-only exit" ``` ### Task 1: 建立统一缓存核心模型与策略 **Files:** - Create: `apps/lib/core/cache/cache_entry.dart` - Create: `apps/lib/core/cache/cache_key.dart` - Create: `apps/lib/core/cache/cache_policy.dart` - Test: `apps/test/core/cache/cache_policy_test.dart` **Step 1: Write the failing test** ```dart import 'package:flutter_test/flutter_test.dart'; import 'package:social_app/core/cache/cache_policy.dart'; void main() { test('soft expired should allow stale read with background refresh', () { final now = DateTime(2026, 3, 20, 12); final policy = CachePolicy( softTtl: const Duration(minutes: 2), hardTtl: const Duration(minutes: 30), minRefreshInterval: const Duration(minutes: 1), ); final fetchedAt = now.subtract(const Duration(minutes: 3)); final decision = policy.evaluate(now: now, fetchedAt: fetchedAt); expect(decision.canUseCached, true); expect(decision.shouldRefreshInBackground, true); expect(decision.mustBlockForNetwork, false); }); } ``` **Step 2: Run test to verify it fails** Run: `cd apps && flutter test test/core/cache/cache_policy_test.dart` Expected: FAIL with missing cache policy symbols. **Step 3: Write minimal implementation** ```dart class CacheDecision { final bool canUseCached; final bool shouldRefreshInBackground; final bool mustBlockForNetwork; const CacheDecision({ required this.canUseCached, required this.shouldRefreshInBackground, required this.mustBlockForNetwork, }); } ``` **Step 4: Run test to verify it passes** Run: `cd apps && flutter test test/core/cache/cache_policy_test.dart` Expected: PASS. **Step 5: Commit** ```bash git add apps/lib/core/cache/cache_entry.dart apps/lib/core/cache/cache_key.dart apps/lib/core/cache/cache_policy.dart apps/test/core/cache/cache_policy_test.dart git commit -m "feat: add unified cache policy primitives" ``` ### Task 2: 实现 memory/persistent/hybrid cache store(含 singleflight) **Files:** - Create: `apps/lib/core/cache/cache_store.dart` - Create: `apps/lib/core/cache/memory_cache_store.dart` - Create: `apps/lib/core/cache/persistent_cache_store.dart` - Create: `apps/lib/core/cache/hybrid_cache_store.dart` - Test: `apps/test/core/cache/hybrid_cache_store_test.dart` **Step 1: Write the failing test** ```dart test('same key concurrent load should execute loader once', () async { var calls = 0; final store = HybridCacheStore(...); Future loader() async { calls += 1; return 'ok'; } await Future.wait([ store.getOrLoad('k', loader: loader), store.getOrLoad('k', loader: loader), ]); expect(calls, 1); }); ``` **Step 2: Run test to verify it fails** Run: `cd apps && flutter test test/core/cache/hybrid_cache_store_test.dart` Expected: FAIL with missing HybridCacheStore. **Step 3: Write minimal implementation** ```dart final Map> _inflight = {}; ``` **Step 4: Run test to verify it passes** Run: `cd apps && flutter test test/core/cache/hybrid_cache_store_test.dart` Expected: PASS. **Step 5: Commit** ```bash git add apps/lib/core/cache/cache_store.dart apps/lib/core/cache/memory_cache_store.dart apps/lib/core/cache/persistent_cache_store.dart apps/lib/core/cache/hybrid_cache_store.dart apps/test/core/cache/hybrid_cache_store_test.dart git commit -m "feat: implement hybrid cache store with singleflight" ``` ### Task 3: 接入 DI 与统一失效器 **Files:** - Create: `apps/lib/core/cache/cache_invalidator.dart` - Modify: `apps/lib/core/di/injection.dart` - Test: `apps/test/core/cache/cache_invalidator_test.dart` **Step 1: Write the failing test** ```dart test('invalidate calendar day should also invalidate month key', () { final inv = CacheInvalidator(...); inv.invalidateCalendarDay(DateTime(2026, 3, 20)); expect(inv.wasInvalidated('calendar:day:2026-03-20'), true); expect(inv.wasInvalidated('calendar:month:2026-03'), true); }); ``` **Step 2: Run test to verify it fails** Run: `cd apps && flutter test test/core/cache/cache_invalidator_test.dart` Expected: FAIL. **Step 3: Write minimal implementation** ```dart class CacheInvalidator { void invalidateCalendarDay(DateTime date) { /* invalidate day + month */ } } ``` **Step 4: Run test to verify it passes** Run: `cd apps && flutter test test/core/cache/cache_invalidator_test.dart` Expected: PASS. **Step 5: Commit** ```bash git add apps/lib/core/cache/cache_invalidator.dart apps/lib/core/di/injection.dart apps/test/core/cache/cache_invalidator_test.dart git commit -m "refactor: wire unified cache and invalidator in di" ``` ### Task 4: 合并个人信息缓存(替换 SettingsUserCache) **Files:** - Modify: `apps/lib/features/settings/data/services/settings_user_cache.dart` - Create: `apps/lib/features/settings/data/services/user_profile_cache_repository.dart` - Modify: `apps/lib/features/settings/ui/screens/settings_screen.dart` - Test: `apps/test/features/settings/data/services/settings_user_cache_test.dart` - Create: `apps/test/features/settings/data/services/user_profile_cache_repository_test.dart` **Step 1: Write the failing test** ```dart test('repository should return persistent cache first then refresh in background', () async { // Arrange cached profile in persistent store // Assert immediate cached result + refresh called once }); ``` **Step 2: Run test to verify it fails** Run: `cd apps && flutter test test/features/settings/data/services/user_profile_cache_repository_test.dart` Expected: FAIL. **Step 3: Write minimal implementation** ```dart class UserProfileCacheRepository { Future getProfile({bool forceRefresh = false}) async { ... } } ``` **Step 4: Run tests to verify they pass** Run: `cd apps && flutter test test/features/settings/data/services/settings_user_cache_test.dart test/features/settings/data/services/user_profile_cache_repository_test.dart` Expected: PASS. **Step 5: Commit** ```bash git add apps/lib/features/settings/data/services/settings_user_cache.dart apps/lib/features/settings/data/services/user_profile_cache_repository.dart apps/lib/features/settings/ui/screens/settings_screen.dart apps/test/features/settings/data/services/settings_user_cache_test.dart apps/test/features/settings/data/services/user_profile_cache_repository_test.dart git commit -m "refactor: merge profile cache into unified cache repository" ``` ### Task 5: 路由改造为 StatefulShellRoute + Dock 切换分支 **Files:** - Modify: `apps/lib/core/router/app_router.dart` - Modify: `apps/lib/core/router/app_routes.dart` - Modify: `apps/lib/features/calendar/ui/widgets/bottom_dock.dart` - Modify: `apps/lib/features/home/ui/navigation/home_return_policy.dart` - Test: `apps/test/core/router/app_routes_test.dart` - Modify: `apps/test/features/home/ui/navigation/home_return_policy_test.dart` **Step 1: Write the failing test** ```dart test('dock home action should always resolve to goHome', () { final action = resolveHomeReturnAction(canPop: true, isAuthEntry: false); expect(action, HomeReturnAction.goHomeForDock); }); ``` **Step 2: Run test to verify it fails** Run: `cd apps && flutter test test/features/home/ui/navigation/home_return_policy_test.dart` Expected: FAIL with old behavior. **Step 3: Write minimal implementation** ```dart enum HomeReturnAction { pop, goHome, goHomeForDock } ``` **Step 4: Run tests to verify they pass** Run: `cd apps && flutter test test/core/router/app_routes_test.dart test/features/home/ui/navigation/home_return_policy_test.dart` Expected: PASS. **Step 5: Commit** ```bash git add apps/lib/core/router/app_router.dart apps/lib/core/router/app_routes.dart apps/lib/features/calendar/ui/widgets/bottom_dock.dart apps/lib/features/home/ui/navigation/home_return_policy.dart apps/test/core/router/app_routes_test.dart apps/test/features/home/ui/navigation/home_return_policy_test.dart git commit -m "feat: switch main navigation to stateful shell tabs" ``` ### Task 6: Calendar repository 化并移除路由监听刷新 **Files:** - Create: `apps/lib/features/calendar/data/services/calendar_repository.dart` - Modify: `apps/lib/features/calendar/ui/screens/calendar_dayweek_screen.dart` - Modify: `apps/lib/features/calendar/ui/screens/calendar_month_screen.dart` - Modify: `apps/lib/features/calendar/ui/calendar_state_manager.dart` - Create: `apps/test/features/calendar/data/services/calendar_repository_test.dart` **Step 1: Write the failing test** ```dart test('getDayEvents returns cache immediately and refreshes in background', () async { // Arrange cache day key // Assert cached list emitted before network completion }); ``` **Step 2: Run test to verify it fails** Run: `cd apps && flutter test test/features/calendar/data/services/calendar_repository_test.dart` Expected: FAIL. **Step 3: Write minimal implementation** ```dart class CalendarRepository { Future> getDayEvents(DateTime date, {bool forceRefresh = false}) async { ... } } ``` **Step 4: Run tests to verify they pass** Run: `cd apps && flutter test test/features/calendar/data/services/calendar_repository_test.dart` Expected: PASS. **Step 5: Commit** ```bash git add apps/lib/features/calendar/data/services/calendar_repository.dart apps/lib/features/calendar/ui/screens/calendar_dayweek_screen.dart apps/lib/features/calendar/ui/screens/calendar_month_screen.dart apps/lib/features/calendar/ui/calendar_state_manager.dart apps/test/features/calendar/data/services/calendar_repository_test.dart git commit -m "refactor: decouple calendar screens from route-driven reload" ``` ### Task 7: Todo repository 化与写后精准失效 **Files:** - Create: `apps/lib/features/todo/data/todo_repository.dart` - Modify: `apps/lib/features/todo/ui/screens/todo_quadrants_screen.dart` - Modify: `apps/lib/features/todo/data/todo_api.dart` - Create: `apps/test/features/todo/todo_repository_test.dart` - Modify: `apps/test/features/todo/quadrant_drag_test.dart` **Step 1: Write the failing test** ```dart test('complete todo should optimistically update and invalidate pending list key', () async { // assert local list updated first, invalidator called }); ``` **Step 2: Run test to verify it fails** Run: `cd apps && flutter test test/features/todo/todo_repository_test.dart` Expected: FAIL. **Step 3: Write minimal implementation** ```dart class TodoRepository { Future completeTodo(String id) async { ... } } ``` **Step 4: Run tests to verify they pass** Run: `cd apps && flutter test test/features/todo/todo_repository_test.dart test/features/todo/quadrant_drag_test.dart` Expected: PASS. **Step 5: Commit** ```bash git add apps/lib/features/todo/data/todo_repository.dart apps/lib/features/todo/ui/screens/todo_quadrants_screen.dart apps/lib/features/todo/data/todo_api.dart apps/test/features/todo/todo_repository_test.dart apps/test/features/todo/quadrant_drag_test.dart git commit -m "feat: add todo cache repository and precise invalidation" ``` ### Task 8: App 生命周期与网络恢复刷新策略 **Files:** - Create: `apps/lib/core/cache/cache_refresh_coordinator.dart` - Modify: `apps/lib/main.dart` - Create: `apps/test/core/cache/cache_refresh_coordinator_test.dart` **Step 1: Write the failing test** ```dart test('resume should trigger refresh only when min interval elapsed', () { // Arrange last refreshed timestamp // Assert callback invocation count }); ``` **Step 2: Run test to verify it fails** Run: `cd apps && flutter test test/core/cache/cache_refresh_coordinator_test.dart` Expected: FAIL. **Step 3: Write minimal implementation** ```dart class CacheRefreshCoordinator with WidgetsBindingObserver { ... } ``` **Step 4: Run test to verify it passes** Run: `cd apps && flutter test test/core/cache/cache_refresh_coordinator_test.dart` Expected: PASS. **Step 5: Commit** ```bash git add apps/lib/core/cache/cache_refresh_coordinator.dart apps/lib/main.dart apps/test/core/cache/cache_refresh_coordinator_test.dart git commit -m "feat: add app lifecycle refresh coordinator" ``` ### Task 9: 提醒取消实时归档与延迟归档兜底收敛 **Files:** - Modify: `apps/lib/features/calendar/reminders/reminder_action_executor.dart` - Modify: `apps/lib/features/calendar/reminders/ui/reminder_foreground_presenter.dart` - Modify: `apps/lib/core/notifications/local_notification_service.dart` - Modify: `apps/lib/main.dart` - Modify: `apps/test/features/calendar/reminders/reminder_action_executor_test.dart` - Modify: `apps/test/features/calendar/reminders/reminder_notification_bridge_test.dart` **Step 1: Write the failing test** ```dart test('archive action should send remote archive immediately when app active', () async { // Arrange active app + online gateway // Act archive action // Assert remote archive called once and local pending outbox not created }); ``` **Step 2: Run test to verify it fails** Run: `cd apps && flutter test test/features/calendar/reminders/reminder_action_executor_test.dart` Expected: FAIL with delayed-only behavior. **Step 3: Write minimal implementation** ```dart if (isAppActive) { await repository.archiveNow(eventId); } else { await outbox.enqueueArchive(eventId); } ``` **Step 4: Run tests to verify they pass** Run: `cd apps && flutter test test/features/calendar/reminders/reminder_action_executor_test.dart test/features/calendar/reminders/reminder_notification_bridge_test.dart` Expected: PASS. **Step 5: Commit** ```bash git add apps/lib/features/calendar/reminders/reminder_action_executor.dart apps/lib/features/calendar/reminders/ui/reminder_foreground_presenter.dart apps/lib/core/notifications/local_notification_service.dart apps/lib/main.dart apps/test/features/calendar/reminders/reminder_action_executor_test.dart apps/test/features/calendar/reminders/reminder_notification_bridge_test.dart git commit -m "fix: prioritize realtime reminder archive with cold-start fallback" ``` ### Task 10: 全量验证与文档同步 **Files:** - Modify: `docs/protocols/*`(仅当路由/数据契约文档需更新时) - Modify: `docs/plans/2026-03-20-navigation-cache-decoupling-design.md`(回填最终参数) **Step 1: Run focused tests** Run: ```bash cd apps && flutter test test/core/cache test/features/settings/data/services/settings_user_cache_test.dart test/features/calendar test/features/todo test/features/home/ui/navigation/home_return_policy_test.dart test/core/router/app_routes_test.dart ``` Expected: PASS. **Step 2: Run app-level verification** Run: `cd apps && flutter test` Expected: PASS. **Step 3: Static checks** Run: `cd apps && flutter analyze` Expected: No errors. **Step 4: Manual verification checklist** 1. 冷启动先显示缓存,随后静默更新。 2. Home/Calendar/Todo 来回切换不重建主页面。 3. 日/月切换不触发无必要请求。 4. Dock Home 始终回主页。 5. 写后数据可见一致,失败可回滚提示。 6. 二级页面侧滑返回只回 Home,不直接退出。 7. 提醒点击取消时立刻归档;仅在 App 不可用时走 pending 兜底。 **Step 5: Commit** ```bash git add docs/plans/2026-03-20-navigation-cache-decoupling-design.md docs/protocols git commit -m "docs: finalize navigation decoupling and unified cache rollout" ``` ## 实施顺序约束 1. 必须先完成 Task 0-3 再改业务页面(否则会出现重复实现)。 2. Task 0(分级返回)与 Task 5(路由壳层)要分开提交,便于单独回滚。 3. 每个 Task 的测试必须在本 Task 完成后立即执行,避免堆积回归。 4. 不允许在未通过 focused tests 的情况下进入全量验证。 ## 回滚策略 1. 若返回语义回归:先回滚 Task 0 提交,再评估 Task 5。 2. 若缓存一致性异常:优先回滚 Task 6/7 的 repository 接入提交。 3. 若生命周期刷新过于频繁:关闭 Task 8 coordinator 挂载,保留手动刷新兜底。 4. 若提醒实时归档异常:回滚 Task 9,仅保留 outbox 兜底路径。 ## Done 定义 1. 所有测试与 analyze 通过。 2. 主页按钮行为稳定,无“返回上一页”误行为。 3. 切换页面请求数明显下降,写后一致性符合设计预期。 4. 统一缓存已接管用户信息、日历、待办三域。 5. 二级页面不再可直接侧滑退出 App。 6. 提醒归档满足“实时优先、关闭兜底”策略。