16 KiB
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: 更新动作语义文案
展示文案“取消”映射到内部动作 archive
- Step 2: 增加幂等键协议
actionExecutionId = notificationId + actionId + fireTimeBucket
- Step 3: 增加 cold-start queue 回放协议
顺序回放,单条失败不阻塞后续
- Step 4: 运行协议自检
Run:
rg "archive|actionExecutionId|cold-start" docs/protocols/calendar/reminder-alert-lifecycle.mdExpected: 命中新增规则 - Step 5: Commit
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 入口)
test('archive action cancels reminder and archives event', () async {});
- Step 2: 运行失败测试
Run (workdir=
apps):flutter test test/features/calendar/reminders/reminder_action_executor_test.dartExpected: FAIL - Step 3: 最小实现(增加 archive 枚举并接入 executor)
enum ReminderAction { archive('archive'), snooze10m('snooze_10m'), ... }
- Step 4: 运行通过测试
Run (workdir=
apps):flutter test test/features/calendar/reminders/reminder_action_executor_test.dartExpected: PASS - Step 5: Commit
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: 写失败测试(重启后仍去重)
test('same actionExecutionId is rejected after restart', () async {});
- Step 2: 跑失败测试
Run (workdir=
apps):flutter test test/features/calendar/reminders/reminder_action_dedupe_store_test.dartExpected: FAIL - Step 3: 实现最小 store
Future<bool> markIfNew(String actionExecutionId)
- Step 4: 跑通过测试
Run (workdir=
apps):flutter test test/features/calendar/reminders/reminder_action_dedupe_store_test.dartExpected: PASS - Step 5: Commit
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: 写失败测试(顺序回放)
test('replays actions in receive order', () async {});
- Step 2: 写失败测试(单条失败不阻塞)
test('continues replay after one action failure', () async {});
- Step 3: 跑失败测试
Run (workdir=
apps):flutter test test/features/calendar/reminders/reminder_cold_start_queue_test.dartExpected: FAIL - Step 4: 最小实现队列
Future<void> replaySequentially(Future<void> Function(...) handler)
- Step 5: 跑通过测试
Run (workdir=
apps):flutter test test/features/calendar/reminders/reminder_cold_start_queue_test.dartExpected: PASS - Step 6: Commit
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)
test('maps cancel action button to ReminderAction.archive', () async {});
- Step 2: 跑失败测试
Run (workdir=
apps):flutter test test/features/calendar/reminders/reminder_notification_bridge_test.dartExpected: FAIL - Step 3: 接入 dedupe store + bridge
if (!await dedupeStore.markIfNew(actionExecutionId)) return;
- Step 4: 跑通过测试
Run (workdir=
apps):flutter test test/features/calendar/reminders/reminder_notification_bridge_test.dartExpected: PASS - Step 5: Commit
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: 写失败测试(仅前台展示)
test('shows reminder sheet only in app active state', () async {});
- Step 2: 写失败测试(去重窗口)
test('suppresses duplicate presentation in dedupe window', () async {});
- Step 3: 跑失败测试
Run (workdir=
apps):flutter test test/features/calendar/reminders/reminder_presentation_coordinator_test.dartExpected: FAIL - Step 4: 最小实现协调器
- Step 5: 跑通过测试
Run (workdir=
apps):flutter test test/features/calendar/reminders/reminder_presentation_coordinator_test.dartExpected: PASS - Step 6: Commit
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: 写失败测试(稍后提醒按钮)
testWidgets('tap snooze triggers snooze callback', (tester) async {});
- Step 2: 写失败测试(归档按钮)
testWidgets('tap archive triggers archive callback', (tester) async {});
- Step 3: 跑失败测试
Run (workdir=
apps):flutter test test/features/calendar/reminders/reminder_action_sheet_test.dartExpected: FAIL - Step 4: 最小实现 token 驱动 UI
- Step 5: 跑通过测试
Run (workdir=
apps):flutter test test/features/calendar/reminders/reminder_action_sheet_test.dartExpected: PASS - Step 6: Commit
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)
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.dartExpected: 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/remindersExpected: PASS - Step 7: Commit
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)
test('android manifest contains ActionBroadcastReceiver', () async {});
- Step 2: 跑失败测试
Run (workdir=
apps):flutter test test/platform/android_manifest_notification_action_test.dartExpected: FAIL - Step 3: 增加 receiver 配置
- Step 4: 跑通过测试
Run (workdir=
apps):flutter test test/platform/android_manifest_notification_action_test.dartExpected: PASS - Step 5: Commit
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)
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.dartExpected: 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.dartExpected: PASS Run (workdir=apps):flutter test test/features/calendar/remindersExpected: PASS - Step 5: Commit
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: 写失败测试(未授权降级到应用内)
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.dartExpected: FAIL - Step 3: 最小实现权限检查 + 降级埋点
- Step 3: 最小实现权限检查 + 降级埋点
埋点字段至少包含:actionExecutionId、permissionState、appLifecycleState、platform
- Step 4: 跑通过测试
Run (workdir=
apps):flutter test test/features/calendar/reminders/reminder_permission_fallback_test.dartExpected: PASS - Step 5: Commit
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/testExpected: 输出待清理命中(仅代码引用,不含注释/文档示例) -
Step 3: 删除无用旧代码、无效测试、旧 fixture
-
Step 4: 清理后扫描 Run:
rg "calendar_reminder_actions_v1|ReminderAction.cancel|_oldReminderEntry|_legacyReminderRoute" apps/lib apps/testExpected: no matches(注释/文档示例允许存在需在清单标注) -
Step 5: Commit
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/remindersExpected: PASS -
Step 2: calendar 相关测试 Run (workdir=
apps):flutter test test/features/calendarExpected: PASS -
Step 3: 手工矩阵 6/6 验证
Android: 前台/后台/杀进程
iOS: 前台/后台锁屏/升级后
- Step 4: 输出证据与指标
命令结果、回放日志、重试日志、重复执行率=0(按 actionExecutionId 统计)
Execution Notes
- 任务顺序不可打乱:协议 -> 动作语义 -> 幂等 -> 回放 -> 桥接 -> UI -> 平台 -> 清理。
- 每个任务只做最小改动并回归对应测试。
- 视觉组件严格使用
apps/lib/core/theme/design_tokens.dart。 - 若实现中与 spec 冲突,先改 spec/协议再继续写代码。