你是 Claude Code,在本仓库内协助完成开发任务。首要目标是按计划稳定推进,保持改动可验证。
- 文件驱动 — 决策写进 PLAN.md / TASKS.md,不依赖聊天记忆
- 单任务聚焦 — 一次只做一件事,做完再下一件
- 测试先行 — 先写测试定义预期,再写实现代码,保证结果的正确性
- 功能解耦 — 每个模块独立可测,不耦合无关逻辑;单文件 ≤500 行,单函数 ≤50 行
- 逐步验证 — 每次改动立即可运行、可检查,不攒大变更
- 注释完善 — 文件、函数、核心逻辑必须有中文文档注释,符合 Dart doc comment 规范
- 文档同步 — 代码改完,立刻更新 TASKS.md(勾选任务、记录完成时间)和 PLAN.md(里程碑进度)
- 最小改动 — 只改当前任务相关的文件和代码,不做额外重构
- 类型安全 — 避免 dynamic、as 强转、!非空断言,优先使用类型安全写法
- 优先保证结构清晰,不要过度设计。
- 按职责拆分,不按页面外观拆分。
- 保持单向数据流:UI 触发动作,controller 更新 state,UI 根据 state 渲染。
- UI 和业务逻辑分离,widget 不承载复杂业务逻辑。
- 优先简单、直接、稳定的方案。
- Screen/Page 负责页面组装、路由参数、读取 provider、分发回调。
- Widget 负责展示和局部交互,尽量保持纯。
- Provider/Notifier 负责状态和业务逻辑。
- Repository 负责数据获取和持久化,不负责业务流程编排。
- Model 表示业务数据,State 表示界面运行状态。Model 不依赖 State,State 可以包含 Model。
- Provider 按功能域拆分,不按组件个数拆分,也不按页面外观拆分。
- 状态只在一个 widget 树内使用且不需要跨组件共享时,用局部 state;否则用 provider。
- 一份真实状态只能有一个单一来源,避免多处维护同一状态。
- 状态变更入口要集中,只能通过明确的方法修改状态。
- 复杂流程提取为纯 Dart 类编排(可测试、可复用),Provider 负责连接编排层和 UI。
- 页面负责组装,组件负责展示。
- 不要做万能组件,避免大量 if 和模式开关。参数超过 10 个说明职责太广,应该拆分。
- build 方法只描述 UI,不做请求、不改状态、不启动副作用。
- 子组件只读取自己关心的状态,避免整页无意义刷新。
- 异步操作必须防竞态:启动时记录标识(token/sessionId),回调时校验标识是否仍有效,过期则丢弃。
- 谁创建谁销毁。资源的生命周期必须和它的所有者绑定,不能由外部隐式管理。
- 副作用(网络请求、文件 IO、平台调用)通过接口或回调注入,不在业务逻辑类中直接调用。
- 每个异步调用点都要考虑失败情况。沉默吞掉异常是 bug,应该明确处理或向上传播。
- 错误、加载、空状态必须显式设计,不要只写成功态。
- 命名优先于技巧,名称必须直接表达职责。
- 目录结构优先按 feature 组织,再在 feature 内部分层。
- 先允许少量重复,确认模式稳定后再抽象。过早抽象比重复更有害。
- 优先测试状态流转和业务逻辑,不要只测 UI 表面。
文档版本: v4.3 更新时间: 2026-04-02
开始任何工作前,必须按顺序完成以下 4 步:
- 读取 PLAN.md — 了解项目当前阶段和整体规划
- 读取 TASKS.md — 了解待办任务列表和优先级
- 输出要执行的任务 — 明确说明接下来做哪一个任务(一次只做一个)
- 等待用户确认 — 用户同意后再开始修改代码
完成当前任务后,必须按顺序完成以下 7 步:
确认以下测试是否已覆盖:
- Unit Test: 纯逻辑测试(模型、服务、辅助类),不涉及 UI
- Widget Test: 组件级 UI 测试
- Integration Test: 端到端(E2E)测试,验证完整用户流程
检查是否存在未使用的代码(包括测试中的),有则删除。
确保新增/修改的代码有清晰的中文注释。
flutter analyze
flutter test
flutter test integration_test -d macos# 必须完成:
1. 勾选已完成任务(- [x])
2. 在任务下添加完成记录:
**完成时间**: 2026-01-31如果本次任务导致里程碑进度变化,必须更新 PLAN.md 中对应里程碑的状态。
**实现的任务**: [任务标题]
**修改的文件** (X 个):
- path/to/file.dart (+50 -10)
**对应的测试**:
- path/to/test_file.dart
**下一步建议**:
- 告诉用户如何验证结果
- 下一个任务是什么满足以下任一条件时,必须执行归档:
- 里程碑完成 — PLAN.md 中某个 Milestone 全部完成
- 文件过大 — TASKS.md 超过 200 行
- 任务过多 — 已完成任务超过 30 条
- 手动触发 — 用户明确要求归档
归档步骤:
- 创建归档文件:
docs/tasks-archive/milestone-X-completed.md - 将已完成任务移入归档文件
- 清理 TASKS.md,仅保留未完成任务
- 在 TASKS.md 顶部添加归档链接
- 更新 PLAN.md 里程碑状态
- 使用
flutter_lints静态分析,配置见analysis_options.yaml - 格式化:
dart format . - 国际化:
flutter_localizations+ ARB 文件(lib/l10n/),模板文件为app_en.arb,当前支持 en / zh
- 框架:
flutter_test+mocktail - 文件命名:
*_test.dart,放在test/对应子目录下 - 每个新功能或 bug 修复必须包含对应测试
- Provider 使用
riverpod_generator代码生成,文件包含part 'xxx.g.dart'; - 修改 Provider 后运行:
dart run build_runner build
- Flutter
dart:ioHttpClient 绕过 iOS 原生网络栈,不会触发系统网络权限弹窗 - 需要通过 Method Channel 调用 iOS 原生
URLSession才能触发 - 当前方案:App 启动时通过
top.echo-loop/networkChannel 发起原生请求(见AppDelegate.swift+main.dart)
记录已修复的典型问题和设计约束,防止同类问题再次出现。
- 现象:录音播放不正确、卡在"分析中"~5 秒、评级却显示很棒
- 根因:
SFSpeechRecognitionTask.cancel()的回调是异步的(排入主队列下一轮事件循环)。若新 session 已启动,旧回调读到新 session 状态并破坏它(isRecording = false+ 取消新识别任务) - 解法:generation counter 模式 — 每次新 session 递增计数器,闭包捕获当前值,回调中校验不匹配则丢弃
- 规则:识别回调的 error 分支不做资源清理,资源清理统一由 stop/cancel/shutdown 发起
- 相关代码:
ios/Runner/AppDelegate.swift→IOSSpeechPracticeHandler - 修复时间:2026-03-27
- 现象:闪卡快速切换单词时,TTS 在 3-9ms 内"完成"(实际还在朗读),倒计时和 TTS 声音同时出现
- 根因:
_tts.stop()触发平台两个响应:① method channel result(解除 await)② cancel handler(异步到达)。cancel handler 在新speak()的 Completer 创建后才到达,错误地完成了新的 Completer。平台层报错Message responses can be sent only once - 解法:不依赖
awaitSpeakCompletion,改用自管理 Completer +setStartHandler标志。start handler 到达前的 completion/cancel 回调视为 stale 直接忽略。method channel FIFO 保证顺序:旧 cancel → 新 start → 新 completion - 规则:TTS 的 stop 和 speak 之间不能依赖 flutter_tts 内部的完成信号隔离,必须自行管理
- 相关代码:
lib/services/tts_service.dart - 修复时间:2026-04-06