Files
social-app/docs/superpowers/plans/2026-03-19-calendar-reminder-unified-interaction-implementation.md
T

16 KiB
Raw Blame History

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.md Expected: 命中新增规则
  • 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.dart Expected: 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.dart Expected: 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.dart Expected: 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.dart Expected: 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.dart Expected: 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.dart Expected: 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.dart Expected: 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.dart Expected: 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.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
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.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
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.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
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.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
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.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
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.dart Expected: FAIL
  • Step 3: 最小实现权限检查 + 降级埋点
  • Step 3: 最小实现权限检查 + 降级埋点
埋点字段至少包含: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
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

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 验证

Android: 前台/后台/杀进程
iOS: 前台/后台锁屏/升级后
  • Step 4: 输出证据与指标
命令结果、回放日志、重试日志、重复执行率=0(按 actionExecutionId 统计)

Execution Notes

  • 任务顺序不可打乱:协议 -> 动作语义 -> 幂等 -> 回放 -> 桥接 -> UI -> 平台 -> 清理。
  • 每个任务只做最小改动并回归对应测试。
  • 视觉组件严格使用 apps/lib/core/theme/design_tokens.dart
  • 若实现中与 spec 冲突,先改 spec/协议再继续写代码。