Skip to content

Latest commit

 

History

History
186 lines (142 loc) · 8.57 KB

File metadata and controls

186 lines (142 loc) · 8.57 KB

CLAUDE.md - Claude Code 工作规范

1 核心原则

你是 Claude Code,在本仓库内协助完成开发任务。首要目标是按计划稳定推进,保持改动可验证。

  1. 文件驱动 — 决策写进 PLAN.md / TASKS.md,不依赖聊天记忆
  2. 单任务聚焦 — 一次只做一件事,做完再下一件
  3. 测试先行 — 先写测试定义预期,再写实现代码,保证结果的正确性
  4. 功能解耦 — 每个模块独立可测,不耦合无关逻辑;单文件 ≤500 行,单函数 ≤50 行
  5. 逐步验证 — 每次改动立即可运行、可检查,不攒大变更
  6. 注释完善 — 文件、函数、核心逻辑必须有中文文档注释,符合 Dart doc comment 规范
  7. 文档同步 — 代码改完,立刻更新 TASKS.md(勾选任务、记录完成时间)和 PLAN.md(里程碑进度)
  8. 最小改动 — 只改当前任务相关的文件和代码,不做额外重构
  9. 类型安全 — 避免 dynamic、as 强转、!非空断言,优先使用类型安全写法

2 Flutter 应用开发原则

2.1 核心原则

  1. 优先保证结构清晰,不要过度设计。
  2. 按职责拆分,不按页面外观拆分。
  3. 保持单向数据流:UI 触发动作,controller 更新 state,UI 根据 state 渲染。
  4. UI 和业务逻辑分离,widget 不承载复杂业务逻辑。
  5. 优先简单、直接、稳定的方案。

2.2 分层职责

  1. Screen/Page 负责页面组装、路由参数、读取 provider、分发回调。
  2. Widget 负责展示和局部交互,尽量保持纯。
  3. Provider/Notifier 负责状态和业务逻辑。
  4. Repository 负责数据获取和持久化,不负责业务流程编排。
  5. Model 表示业务数据,State 表示界面运行状态。Model 不依赖 State,State 可以包含 Model。

2.3 状态管理

  1. Provider 按功能域拆分,不按组件个数拆分,也不按页面外观拆分。
  2. 状态只在一个 widget 树内使用且不需要跨组件共享时,用局部 state;否则用 provider。
  3. 一份真实状态只能有一个单一来源,避免多处维护同一状态。
  4. 状态变更入口要集中,只能通过明确的方法修改状态。
  5. 复杂流程提取为纯 Dart 类编排(可测试、可复用),Provider 负责连接编排层和 UI。

2.4 Widget 设计

  1. 页面负责组装,组件负责展示。
  2. 不要做万能组件,避免大量 if 和模式开关。参数超过 10 个说明职责太广,应该拆分。
  3. build 方法只描述 UI,不做请求、不改状态、不启动副作用。
  4. 子组件只读取自己关心的状态,避免整页无意义刷新。

2.5 可靠性

  1. 异步操作必须防竞态:启动时记录标识(token/sessionId),回调时校验标识是否仍有效,过期则丢弃。
  2. 谁创建谁销毁。资源的生命周期必须和它的所有者绑定,不能由外部隐式管理。
  3. 副作用(网络请求、文件 IO、平台调用)通过接口或回调注入,不在业务逻辑类中直接调用。
  4. 每个异步调用点都要考虑失败情况。沉默吞掉异常是 bug,应该明确处理或向上传播。
  5. 错误、加载、空状态必须显式设计,不要只写成功态。

2.6 可维护性

  1. 命名优先于技巧,名称必须直接表达职责。
  2. 目录结构优先按 feature 组织,再在 feature 内部分层。
  3. 先允许少量重复,确认模式稳定后再抽象。过早抽象比重复更有害。
  4. 优先测试状态流转和业务逻辑,不要只测 UI 表面。

文档版本: v4.3 更新时间: 2026-04-02


3 启动流程(每个会话强制执行)

开始任何工作前,必须按顺序完成以下 4 步:

  1. 读取 PLAN.md — 了解项目当前阶段和整体规划
  2. 读取 TASKS.md — 了解待办任务列表和优先级
  3. 输出要执行的任务 — 明确说明接下来做哪一个任务(一次只做一个)
  4. 等待用户确认 — 用户同意后再开始修改代码

4 收尾流程(每次完成任务强制执行)

完成当前任务后,必须按顺序完成以下 7 步:

步骤 1: 检查测试完整性

确认以下测试是否已覆盖:

  • Unit Test: 纯逻辑测试(模型、服务、辅助类),不涉及 UI
  • Widget Test: 组件级 UI 测试
  • Integration Test: 端到端(E2E)测试,验证完整用户流程

步骤 2: 删除死代码

检查是否存在未使用的代码(包括测试中的),有则删除。

步骤 3: 检查注释和文档

确保新增/修改的代码有清晰的中文注释。

步骤 4: 运行验证命令

flutter analyze
flutter test
flutter test integration_test -d macos

步骤 5: 更新 TASKS.md

# 必须完成:
1. 勾选已完成任务(- [x]2. 在任务下添加完成记录:

  **完成时间**: 2026-01-31

步骤 6: 更新 PLAN.md(如有需要)

如果本次任务导致里程碑进度变化,必须更新 PLAN.md 中对应里程碑的状态。

步骤 7: 输出完成摘要

**实现的任务**: [任务标题]
**修改的文件** (X 个):
- path/to/file.dart (+50 -10)
**对应的测试**:
- path/to/test_file.dart
**下一步建议**:
- 告诉用户如何验证结果
- 下一个任务是什么

5 TASKS.md 归档规则

满足以下任一条件时,必须执行归档:

  1. 里程碑完成 — PLAN.md 中某个 Milestone 全部完成
  2. 文件过大 — TASKS.md 超过 200 行
  3. 任务过多 — 已完成任务超过 30 条
  4. 手动触发 — 用户明确要求归档

归档步骤:

  1. 创建归档文件:docs/tasks-archive/milestone-X-completed.md
  2. 将已完成任务移入归档文件
  3. 清理 TASKS.md,仅保留未完成任务
  4. 在 TASKS.md 顶部添加归档链接
  5. 更新 PLAN.md 里程碑状态

6 编码规范

6.1 Dart / Flutter 约定

  • 使用 flutter_lints 静态分析,配置见 analysis_options.yaml
  • 格式化:dart format .
  • 国际化:flutter_localizations + ARB 文件(lib/l10n/),模板文件为 app_en.arb,当前支持 en / zh

6.2 测试约定

  • 框架:flutter_test + mocktail
  • 文件命名:*_test.dart,放在 test/ 对应子目录下
  • 每个新功能或 bug 修复必须包含对应测试

6.3 Riverpod 代码生成

  • Provider 使用 riverpod_generator 代码生成,文件包含 part 'xxx.g.dart';
  • 修改 Provider 后运行:dart run build_runner build

6.4 iOS 网络注意事项

  • Flutter dart:io HttpClient 绕过 iOS 原生网络栈,不会触发系统网络权限弹窗
  • 需要通过 Method Channel 调用 iOS 原生 URLSession 才能触发
  • 当前方案:App 启动时通过 top.echo-loop/network Channel 发起原生请求(见 AppDelegate.swift + main.dart

7 Troubleshooting(踩坑记录)

记录已修复的典型问题和设计约束,防止同类问题再次出现。

7.1 iOS 语音识别:异步回调破坏新 session

  • 现象:录音播放不正确、卡在"分析中"~5 秒、评级却显示很棒
  • 根因SFSpeechRecognitionTask.cancel() 的回调是异步的(排入主队列下一轮事件循环)。若新 session 已启动,旧回调读到新 session 状态并破坏它(isRecording = false + 取消新识别任务)
  • 解法:generation counter 模式 — 每次新 session 递增计数器,闭包捕获当前值,回调中校验不匹配则丢弃
  • 规则:识别回调的 error 分支不做资源清理,资源清理统一由 stop/cancel/shutdown 发起
  • 相关代码ios/Runner/AppDelegate.swiftIOSSpeechPracticeHandler
  • 修复时间:2026-03-27

7.2 flutter_tts:快速 stop→speak 导致 awaitSpeakCompletion 失效

  • 现象:闪卡快速切换单词时,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