# Calendar Reminder Unified Interaction Implementation Plan > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. **Goal:** 交付“系统通知主触达 + 前台统一提醒面板”的提醒链路,保证 iOS/Android 在前台、后台、终止态都可执行“稍后提醒”和“取消并归档(内部 archive)”。 **Architecture:** `LocalNotificationService` 仅做调度与平台桥接;`ReminderActionExecutor` 作为唯一动作执行器;新增 `ReminderPresentationCoordinator` + `ReminderActionSheet` 负责前台展示;新增持久化幂等与 cold-start 回放,避免动作丢失/重复执行。 **Tech Stack:** Flutter, flutter_local_notifications, SharedPreferences, Dart isolate callback, AndroidManifest receiver, iOS AppDelegate callback, Flutter tests. --- ## File Structure (Locked Before Tasks) ### Protocol (Must First) - Modify: `docs/protocols/calendar/reminder-alert-lifecycle.md` ### Reminder Core - Modify: `apps/lib/core/notifications/local_notification_service.dart` - Create: `apps/lib/core/notifications/reminder_notification_callbacks.dart` - Modify: `apps/lib/features/calendar/reminders/models/reminder_action.dart` - Modify: `apps/lib/features/calendar/reminders/reminder_action_executor.dart` - Create: `apps/lib/features/calendar/reminders/reminder_action_dedupe_store.dart` - Create: `apps/lib/features/calendar/reminders/reminder_cold_start_queue.dart` ### Foreground UI - Create: `apps/lib/features/calendar/reminders/ui/reminder_presentation_coordinator.dart` - Create: `apps/lib/features/calendar/reminders/ui/widgets/reminder_action_sheet.dart` ### App & Platform Wiring - Modify: `apps/lib/main.dart` - Modify: `apps/android/app/src/main/AndroidManifest.xml` - Modify: `apps/ios/Runner/AppDelegate.swift` ### Tests - Modify: `apps/test/features/calendar/reminders/reminder_action_executor_test.dart` - Create: `apps/test/features/calendar/reminders/reminder_action_dedupe_store_test.dart` - Create: `apps/test/features/calendar/reminders/reminder_cold_start_queue_test.dart` - Create: `apps/test/features/calendar/reminders/reminder_notification_bridge_test.dart` - Create: `apps/test/features/calendar/reminders/reminder_presentation_coordinator_test.dart` - Create: `apps/test/features/calendar/reminders/reminder_action_sheet_test.dart` - Create: `apps/test/features/calendar/reminders/reminder_permission_fallback_test.dart` - Create: `apps/test/platform/android_manifest_notification_action_test.dart` - Create: `apps/test/platform/ios_app_delegate_notification_callback_test.dart` ### Cleanup - Create: `docs/todo/calendar-reminder-migration-checklist.md` --- ### Task 0: 协议先行更新(必须) **Files:** - Modify: `docs/protocols/calendar/reminder-alert-lifecycle.md` - [ ] **Step 1: 更新动作语义文案** ```text 展示文案“取消”映射到内部动作 archive ``` - [ ] **Step 2: 增加幂等键协议** ```text actionExecutionId = notificationId + actionId + fireTimeBucket ``` - [ ] **Step 3: 增加 cold-start queue 回放协议** ```text 顺序回放,单条失败不阻塞后续 ``` - [ ] **Step 4: 运行协议自检** Run: `rg "archive|actionExecutionId|cold-start" docs/protocols/calendar/reminder-alert-lifecycle.md` Expected: 命中新增规则 - [ ] **Step 5: Commit** ```bash git add docs/protocols/calendar/reminder-alert-lifecycle.md git commit -m "docs: align reminder protocol with archive and idempotency" ``` ### Task 1: 动作语义收敛(TDD) **Files:** - Modify: `apps/test/features/calendar/reminders/reminder_action_executor_test.dart` - Modify: `apps/lib/features/calendar/reminders/models/reminder_action.dart` - Modify: `apps/lib/features/calendar/reminders/reminder_action_executor.dart` - [ ] **Step 1: 写失败测试(archive 入口)** ```dart test('archive action cancels reminder and archives event', () async {}); ``` - [ ] **Step 2: 运行失败测试** Run (workdir=`apps`): `flutter test test/features/calendar/reminders/reminder_action_executor_test.dart` Expected: FAIL - [ ] **Step 3: 最小实现(增加 archive 枚举并接入 executor)** ```dart enum ReminderAction { archive('archive'), snooze10m('snooze_10m'), ... } ``` - [ ] **Step 4: 运行通过测试** Run (workdir=`apps`): `flutter test test/features/calendar/reminders/reminder_action_executor_test.dart` Expected: PASS - [ ] **Step 5: Commit** ```bash git add apps/lib/features/calendar/reminders/models/reminder_action.dart apps/lib/features/calendar/reminders/reminder_action_executor.dart apps/test/features/calendar/reminders/reminder_action_executor_test.dart git commit -m "refactor: unify reminder action semantics to archive" ``` ### Task 2: 持久化幂等(TDD) **Files:** - Create: `apps/test/features/calendar/reminders/reminder_action_dedupe_store_test.dart` - Create: `apps/lib/features/calendar/reminders/reminder_action_dedupe_store.dart` - [ ] **Step 1: 写失败测试(重启后仍去重)** ```dart test('same actionExecutionId is rejected after restart', () async {}); ``` - [ ] **Step 2: 跑失败测试** Run (workdir=`apps`): `flutter test test/features/calendar/reminders/reminder_action_dedupe_store_test.dart` Expected: FAIL - [ ] **Step 3: 实现最小 store** ```dart Future markIfNew(String actionExecutionId) ``` - [ ] **Step 4: 跑通过测试** Run (workdir=`apps`): `flutter test test/features/calendar/reminders/reminder_action_dedupe_store_test.dart` Expected: PASS - [ ] **Step 5: Commit** ```bash git add apps/lib/features/calendar/reminders/reminder_action_dedupe_store.dart apps/test/features/calendar/reminders/reminder_action_dedupe_store_test.dart git commit -m "feat: add persistent reminder action dedupe store" ``` ### Task 3: 冷启动回放队列(TDD) **Files:** - Create: `apps/test/features/calendar/reminders/reminder_cold_start_queue_test.dart` - Create: `apps/lib/features/calendar/reminders/reminder_cold_start_queue.dart` - [ ] **Step 1: 写失败测试(顺序回放)** ```dart test('replays actions in receive order', () async {}); ``` - [ ] **Step 2: 写失败测试(单条失败不阻塞)** ```dart test('continues replay after one action failure', () async {}); ``` - [ ] **Step 3: 跑失败测试** Run (workdir=`apps`): `flutter test test/features/calendar/reminders/reminder_cold_start_queue_test.dart` Expected: FAIL - [ ] **Step 4: 最小实现队列** ```dart Future replaySequentially(Future Function(...) handler) ``` - [ ] **Step 5: 跑通过测试** Run (workdir=`apps`): `flutter test test/features/calendar/reminders/reminder_cold_start_queue_test.dart` Expected: PASS - [ ] **Step 6: Commit** ```bash git add apps/lib/features/calendar/reminders/reminder_cold_start_queue.dart apps/test/features/calendar/reminders/reminder_cold_start_queue_test.dart git commit -m "feat: add reminder cold-start replay queue" ``` ### Task 4: 通知桥接映射(TDD) **Files:** - Create: `apps/test/features/calendar/reminders/reminder_notification_bridge_test.dart` - Modify: `apps/lib/core/notifications/local_notification_service.dart` - [ ] **Step 1: 写失败测试(cancel 文案映射 archive)** ```dart test('maps cancel action button to ReminderAction.archive', () async {}); ``` - [ ] **Step 2: 跑失败测试** Run (workdir=`apps`): `flutter test test/features/calendar/reminders/reminder_notification_bridge_test.dart` Expected: FAIL - [ ] **Step 3: 接入 dedupe store + bridge** ```dart if (!await dedupeStore.markIfNew(actionExecutionId)) return; ``` - [ ] **Step 4: 跑通过测试** Run (workdir=`apps`): `flutter test test/features/calendar/reminders/reminder_notification_bridge_test.dart` Expected: PASS - [ ] **Step 5: Commit** ```bash git add apps/lib/core/notifications/local_notification_service.dart apps/test/features/calendar/reminders/reminder_notification_bridge_test.dart git commit -m "feat: map reminder notification actions through unified bridge" ``` ### Task 5: 前台协调器(TDD) **Files:** - Create: `apps/test/features/calendar/reminders/reminder_presentation_coordinator_test.dart` - Create: `apps/lib/features/calendar/reminders/ui/reminder_presentation_coordinator.dart` - [ ] **Step 1: 写失败测试(仅前台展示)** ```dart test('shows reminder sheet only in app active state', () async {}); ``` - [ ] **Step 2: 写失败测试(去重窗口)** ```dart test('suppresses duplicate presentation in dedupe window', () async {}); ``` - [ ] **Step 3: 跑失败测试** Run (workdir=`apps`): `flutter test test/features/calendar/reminders/reminder_presentation_coordinator_test.dart` Expected: FAIL - [ ] **Step 4: 最小实现协调器** - [ ] **Step 5: 跑通过测试** Run (workdir=`apps`): `flutter test test/features/calendar/reminders/reminder_presentation_coordinator_test.dart` Expected: PASS - [ ] **Step 6: Commit** ```bash git add apps/lib/features/calendar/reminders/ui/reminder_presentation_coordinator.dart apps/test/features/calendar/reminders/reminder_presentation_coordinator_test.dart git commit -m "feat: add reminder foreground presentation coordinator" ``` ### Task 6: 前台提醒面板组件(TDD) **Files:** - Create: `apps/test/features/calendar/reminders/reminder_action_sheet_test.dart` - Create: `apps/lib/features/calendar/reminders/ui/widgets/reminder_action_sheet.dart` - [ ] **Step 1: 写失败测试(稍后提醒按钮)** ```dart testWidgets('tap snooze triggers snooze callback', (tester) async {}); ``` - [ ] **Step 2: 写失败测试(归档按钮)** ```dart testWidgets('tap archive triggers archive callback', (tester) async {}); ``` - [ ] **Step 3: 跑失败测试** Run (workdir=`apps`): `flutter test test/features/calendar/reminders/reminder_action_sheet_test.dart` Expected: FAIL - [ ] **Step 4: 最小实现 token 驱动 UI** - [ ] **Step 5: 跑通过测试** Run (workdir=`apps`): `flutter test test/features/calendar/reminders/reminder_action_sheet_test.dart` Expected: PASS - [ ] **Step 6: Commit** ```bash git add apps/lib/features/calendar/reminders/ui/widgets/reminder_action_sheet.dart apps/test/features/calendar/reminders/reminder_action_sheet_test.dart git commit -m "feat: add reusable reminder action sheet" ``` ### Task 7: App 接线与后台入口(TDD) **Files:** - Modify: `apps/lib/main.dart` - Create/Modify: `apps/lib/core/notifications/reminder_notification_callbacks.dart` - Modify: `apps/lib/core/notifications/local_notification_service.dart` - Create: `apps/test/features/calendar/reminders/reminder_notification_callbacks_test.dart` - [ ] **Step 1: 写失败测试(后台入口是 top-level + pragma)** ```dart test('background notification callback is top-level entry-point', () async {}); ``` - [ ] **Step 2: 跑失败测试** Run (workdir=`apps`): `flutter test test/features/calendar/reminders/reminder_notification_callbacks_test.dart` Expected: FAIL - [ ] **Step 3: 接线 initialize 注册前台/后台回调** - [ ] **Step 4: 接线 foreground presenter 到 coordinator** - [ ] **Step 5: 接线 action handler 到 executor** - [ ] **Step 6: reminders 套件回归** Run (workdir=`apps`): `flutter test test/features/calendar/reminders` Expected: PASS - [ ] **Step 7: Commit** ```bash git add apps/lib/main.dart apps/lib/core/notifications/reminder_notification_callbacks.dart apps/lib/core/notifications/local_notification_service.dart apps/test/features/calendar/reminders/reminder_notification_callbacks_test.dart git commit -m "feat: wire reminder callbacks for foreground and background" ``` ### Task 8: Android 平台配置可执行校验(TDD) **Files:** - Create: `apps/test/platform/android_manifest_notification_action_test.dart` - Modify: `apps/android/app/src/main/AndroidManifest.xml` - [ ] **Step 1: 写失败测试(缺 ActionBroadcastReceiver)** ```dart test('android manifest contains ActionBroadcastReceiver', () async {}); ``` - [ ] **Step 2: 跑失败测试** Run (workdir=`apps`): `flutter test test/platform/android_manifest_notification_action_test.dart` Expected: FAIL - [ ] **Step 3: 增加 receiver 配置** - [ ] **Step 4: 跑通过测试** Run (workdir=`apps`): `flutter test test/platform/android_manifest_notification_action_test.dart` Expected: PASS - [ ] **Step 5: Commit** ```bash git add apps/android/app/src/main/AndroidManifest.xml apps/test/platform/android_manifest_notification_action_test.dart git commit -m "fix: register android action receiver for reminder notifications" ``` ### Task 9: iOS 平台配置可执行校验(TDD) **Files:** - Create: `apps/test/platform/ios_app_delegate_notification_callback_test.dart` - Modify: `apps/ios/Runner/AppDelegate.swift` - Modify: `apps/lib/core/notifications/local_notification_service.dart` - [ ] **Step 1: 写失败测试(registrant callback)** ```dart test('ios app delegate registers flutter local notifications callback', () async {}); ``` - [ ] **Step 2: 跑失败测试** Run (workdir=`apps`): `flutter test test/platform/ios_app_delegate_notification_callback_test.dart` Expected: FAIL - [ ] **Step 3: 实现 callback 注册 + category version bump (`calendar_reminder_v2`)** - [ ] **Step 4: 跑通过测试与 reminders 回归** Run (workdir=`apps`): `flutter test test/platform/ios_app_delegate_notification_callback_test.dart` Expected: PASS Run (workdir=`apps`): `flutter test test/features/calendar/reminders` Expected: PASS - [ ] **Step 5: Commit** ```bash git add apps/ios/Runner/AppDelegate.swift apps/lib/core/notifications/local_notification_service.dart apps/test/platform/ios_app_delegate_notification_callback_test.dart git commit -m "fix: enable ios reminder action handling in background" ``` ### Task 10: Android 13+ 权限降级与埋点(TDD) **Files:** - Create: `apps/test/features/calendar/reminders/reminder_permission_fallback_test.dart` - Modify: `apps/lib/core/notifications/local_notification_service.dart` - [ ] **Step 1: 写失败测试(未授权降级到应用内)** ```dart test('fallbacks to in-app path when notifications permission denied', () async {}); ``` - [ ] **Step 2: 跑失败测试** Run (workdir=`apps`): `flutter test test/features/calendar/reminders/reminder_permission_fallback_test.dart` Expected: FAIL - [ ] **Step 3: 最小实现权限检查 + 降级埋点** - [ ] **Step 3: 最小实现权限检查 + 降级埋点** ```text 埋点字段至少包含:actionExecutionId、permissionState、appLifecycleState、platform ``` - [ ] **Step 4: 跑通过测试** Run (workdir=`apps`): `flutter test test/features/calendar/reminders/reminder_permission_fallback_test.dart` Expected: PASS - [ ] **Step 5: Commit** ```bash git add apps/lib/core/notifications/local_notification_service.dart apps/test/features/calendar/reminders/reminder_permission_fallback_test.dart git commit -m "feat: add reminder permission fallback path and telemetry" ``` ### Task 11: 旧代码清单与即时清理 **Files:** - Create: `docs/todo/calendar-reminder-migration-checklist.md` - Modify/Delete: `apps/lib/**`, `apps/test/**`(以清单为准) - [ ] **Step 1: 建立迁移清单(文件/符号/决策/责任人)** - [ ] **Step 2: 清理前扫描** Run: `rg "calendar_reminder_actions_v1|ReminderAction.cancel|_oldReminderEntry|_legacyReminderRoute" apps/lib apps/test` Expected: 输出待清理命中(仅代码引用,不含注释/文档示例) - [ ] **Step 3: 删除无用旧代码、无效测试、旧 fixture** - [ ] **Step 4: 清理后扫描** Run: `rg "calendar_reminder_actions_v1|ReminderAction.cancel|_oldReminderEntry|_legacyReminderRoute" apps/lib apps/test` Expected: no matches(注释/文档示例允许存在需在清单标注) - [ ] **Step 5: Commit** ```bash git add docs/todo/calendar-reminder-migration-checklist.md apps/lib apps/test git commit -m "refactor: remove obsolete reminder paths after migration" ``` ### Task 12: 最终验证与交付 **Files:** - Verify only - [ ] **Step 1: reminders 全量测试** Run (workdir=`apps`): `flutter test test/features/calendar/reminders` Expected: PASS - [ ] **Step 2: calendar 相关测试** Run (workdir=`apps`): `flutter test test/features/calendar` Expected: PASS - [ ] **Step 3: 手工矩阵 6/6 验证** ```text Android: 前台/后台/杀进程 iOS: 前台/后台锁屏/升级后 ``` - [ ] **Step 4: 输出证据与指标** ```text 命令结果、回放日志、重试日志、重复执行率=0(按 actionExecutionId 统计) ``` --- ## Execution Notes - 任务顺序不可打乱:协议 -> 动作语义 -> 幂等 -> 回放 -> 桥接 -> UI -> 平台 -> 清理。 - 每个任务只做最小改动并回归对应测试。 - 视觉组件严格使用 `apps/lib/core/theme/design_tokens.dart`。 - 若实现中与 spec 冲突,先改 spec/协议再继续写代码。