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

392 lines
16 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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<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**
```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<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**
```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/协议再继续写代码。