docs(w2): mark plan tasks 1-7/9 done + sync CLAUDE.md §8 + write W2 retro

- plan: flip 43 checkboxes done across Task 1-7/9; Task 8 (manual speed
  baseline) and Task 10 (this retro) intentionally left open
- CLAUDE.md §8: AI/ ⚠️ partial (AIRuntime/LLMSession/ModelStore/TokenChunk
  done, VLSession/Prompts/ pending); FileVault ; add Debug/DebugAIRunner ;
  drop bold from "W2 当前" and tag W2-W3 row 进行中
- new retros/2026-05-31-w2.md: status table, TBD speed baseline,
  off-plan Symptom/Timeline/ArchiveListView/AppIcon/Swift6 cleanup,
  Swift 6 + Simulator sandbox learnings, W3 prep checklist
This commit is contained in:
link2026
2026-05-25 23:36:16 +08:00
parent e3ad24ac0e
commit b80fae35c9
3 changed files with 90 additions and 47 deletions

View File

@@ -219,9 +219,10 @@ C2 解读 Tab 底部显示一段 diff 文本,**由 `ReportCompareService` 计算
└── Me/ ❌ 只有 placeholder
待建:
├── AI/ AIRuntime, LLMSession, VLSession, Prompts/
├── AI/ ⚠️ AIRuntime + LLMSession + ModelStore + TokenChunk ✅;VLSession + Prompts/
├── Debug/DebugAIRunner.swift ✅ DEBUG-only AI 自检入口
├── Services/ ❌ CaptureService, AskService, TrendService, ReportCompareService
├── Persistence/FileVault.swift 原图加密目录管理
├── Persistence/FileVault.swift 原图加密目录管理
├── Security/AppLock.swift ❌ Face ID 启动锁
├── Features/Ask/ ❌ AskSheet (RAG 问答 UI)
├── Features/Archive/
@@ -265,8 +266,8 @@ C2 解读 Tab 底部显示一段 diff 文本,**由 `ReportCompareService` 计算
| 周次 | 必交付 |
|---|---|
| W1 末 / **W2 当前** | 项目结构、MLX 跑通 Qwen3-1.7B、首个 token 在设备吐出 |
| W2-W3 | AIRuntime + LLMSession,文字日记 + 基础 RAG 问答(打字机效果) |
| W1 末 / W2 当前 | 项目结构、MLX 跑通 Qwen3-1.7B、首个 token 在设备吐出 |
| W2-W3 | AIRuntime + LLMSession,文字日记 + 基础 RAG 问答(打字机效果)(W2 进行中) |
| W3-W4 | VLSession + 统一拍照流程(单项 + 整份)、Asset / FileVault |
| W4 末 | **C1 ArchiveListView**(分类 chip + 年份分组,接 @Query) |
| W4-W5 | 趋势(Swift Charts + AI 解读)、**C2 ReportDetailView**(三 Tab + 重新解读) |

View File

@@ -47,13 +47,13 @@
**Files:**
- Modify: `康康.xcodeproj/project.pbxproj`(通过 Xcode UI 修改,不要手编)
- [ ] **Step 1:打开 Xcode 项目**
- [x] **Step 1:打开 Xcode 项目**
```bash
open /Users/xuhuayong/apps/康康/康康.xcodeproj
```
- [ ] **Step 2:加入 MLX Swift 依赖**
- [x] **Step 2:加入 MLX Swift 依赖**
在 Xcode → File → Add Package Dependencies → 输入 URL:
@@ -68,7 +68,7 @@ https://github.com/ml-explore/mlx-swift
- `MLXOptimizers`
- `MLXRandom`
- [ ] **Step 3:加入 mlx-swift-examples(含 LLM 工具)**
- [x] **Step 3:加入 mlx-swift-examples(含 LLM 工具)**
继续 Add Package Dependencies,URL:
@@ -78,19 +78,19 @@ https://github.com/ml-explore/mlx-swift-examples
勾选 `MLXLLM``MLXLMCommon` 加到 **康康** target。
- [ ] **Step 4:确认 Build Settings**
- [x] **Step 4:确认 Build Settings**
Xcode → 康康 target → Build Settings → 搜 "Swift Language Version" → 确认 Swift 5(MLX 不支持 Swift 6 严格并发)。
康康 target → General → Minimum Deployments → iOS 17.0(MLX 要求)。
- [ ] **Step 5:Build 验证**
- [x] **Step 5:Build 验证**
Xcode 顶部选模拟器(任何一个 iPhone 15+),按 ⌘B。
Expected:Build Succeeded,无依赖错误。
- [ ] **Step 6:提交**
- [x] **Step 6:提交**
```bash
cd /Users/xuhuayong/apps/康康
@@ -105,7 +105,7 @@ git commit -m "build: add MLX Swift SPM dependencies"
**Files:**
- Modify: `康康/Models/Models.swift`(全文重写)
- [ ] **Step 1:把 Models.swift 替换为新内容**
- [x] **Step 1:把 Models.swift 替换为新内容**
打开 `康康/Models/Models.swift`,**整文件替换**为:
@@ -268,7 +268,7 @@ final class ChatTurn {
}
```
- [ ] **Step 2:更新 KangkangApp.swift Schema**
- [x] **Step 2:更新 KangkangApp.swift Schema**
打开 `康康/App/KangkangApp.swift`,替换 Schema 数组:
@@ -282,7 +282,7 @@ let schema = Schema([
])
```
- [ ] **Step 3:删模拟器沙盒(破坏性迁移)**
- [x] **Step 3:删模拟器沙盒(破坏性迁移)**
在 Mac 上:
@@ -293,13 +293,13 @@ xcrun simctl erase all
(也可以在 Simulator → Device → Erase All Content and Settings)
- [ ] **Step 4:Build & Run 验证**
- [x] **Step 4:Build & Run 验证**
Xcode ⌘R 运行到模拟器,App 启动不崩 = Schema OK。
Expected:App 启动到 RootView,无 fatalError。
- [ ] **Step 5:提交**
- [x] **Step 5:提交**
```bash
git add 康康/Models/Models.swift 康康/App/KangkangApp.swift
@@ -314,7 +314,7 @@ git commit -m "feat(models): add Asset/ChatTurn, indicator-report relationship,
- Create: `康康/Persistence/FileVault.swift`
- Test: `康康Tests/FileVaultTests.swift`
- [ ] **Step 1:写失败的测试**
- [x] **Step 1:写失败的测试**
创建 `康康Tests/FileVaultTests.swift`:
@@ -369,13 +369,13 @@ struct FileVaultTests {
}
```
- [ ] **Step 2:运行测试,确认 fail**
- [x] **Step 2:运行测试,确认 fail**
Xcode ⌘U 跑测试(在模拟器上跑)。
Expected:`FileVaultTests` 编译错误 "Cannot find 'FileVault' in scope"。
- [ ] **Step 3:写最小 FileVault 实现**
- [x] **Step 3:写最小 FileVault 实现**
创建 `康康/Persistence/FileVault.swift`:
@@ -454,19 +454,19 @@ final class FileVault {
}
```
- [ ] **Step 4:把 FileVault.swift 加入 康康 target**
- [x] **Step 4:把 FileVault.swift 加入 康康 target**
Xcode 右键 `康康/` 目录 → New Group "Persistence" → 把 FileVault.swift 拖进去,确认 Target Membership 勾选 "康康"。
把 FileVaultTests.swift 拖进 康康Tests target,确认 Target Membership 勾选 "康康Tests"。
- [ ] **Step 5:跑测试,确认全 pass**
- [x] **Step 5:跑测试,确认全 pass**
Xcode ⌘U。
Expected:`writeAndReadJPEGRoundtrip` / `removeMakesFileGone` / `wipeRemovesAllFiles` 全绿。
- [ ] **Step 6:提交**
- [x] **Step 6:提交**
```bash
git add 康康/Persistence/FileVault.swift 康康Tests/FileVaultTests.swift 康康.xcodeproj
@@ -481,7 +481,7 @@ git commit -m "feat(persistence): add FileVault with complete file protection"
- Create: `康康/AI/ModelStore.swift`
- Test: `康康Tests/ModelStoreTests.swift`
- [ ] **Step 1:写失败的测试**
- [x] **Step 1:写失败的测试**
创建 `康康Tests/ModelStoreTests.swift`:
@@ -531,11 +531,11 @@ struct ModelStoreTests {
}
```
- [ ] **Step 2:运行测试,确认 fail**
- [x] **Step 2:运行测试,确认 fail**
⌘U → expect `Cannot find 'ModelStore'`.
- [ ] **Step 3:写 ModelStore 实现**
- [x] **Step 3:写 ModelStore 实现**
创建 `康康/AI/ModelStore.swift`:
@@ -619,18 +619,18 @@ final class ModelStore {
}
```
- [ ] **Step 4:Xcode 中把文件加入 target**
- [x] **Step 4:Xcode 中把文件加入 target**
右键 `康康/` → New Group "AI" → 拖入 ModelStore.swift,勾 "康康" target。
ModelStoreTests.swift 拖入 康康Tests target。
- [ ] **Step 5:跑测试,全绿**
- [x] **Step 5:跑测试,全绿**
⌘U。
Expected:3 个测试全 pass。
- [ ] **Step 6:提交**
- [x] **Step 6:提交**
```bash
git add 康康/AI/ModelStore.swift 康康Tests/ModelStoreTests.swift 康康.xcodeproj
@@ -647,7 +647,7 @@ git commit -m "feat(ai): add ModelStore with path management and bundle seed"
本任务**不接 MLX**,只搭骨架。Task 6 才接真模型。
- [ ] **Step 1:创建 TokenChunk.swift**
- [x] **Step 1:创建 TokenChunk.swift**
```swift
import Foundation
@@ -658,7 +658,7 @@ struct TokenChunk: Sendable {
}
```
- [ ] **Step 2:创建 AIRuntime.swift 骨架**
- [x] **Step 2:创建 AIRuntime.swift 骨架**
```swift
import Foundation
@@ -754,19 +754,19 @@ actor AIRuntime {
}
```
- [ ] **Step 3:确认 Build 失败原因合理**
- [x] **Step 3:确认 Build 失败原因合理**
⌘B → expect "Cannot find 'LLMSession' in scope"(Task 6 才会建)。
这是预期。我们要让 Task 6 写完后 AIRuntime 直接能工作。
- [ ] **Step 4:把文件加入 target**
- [x] **Step 4:把文件加入 target**
把 TokenChunk.swift 和 AIRuntime.swift 拖进 AI group,勾 "康康" target。
(此时 Build 还是失败,正常)
- [ ] **Step 5:暂不提交**
- [x] **Step 5:暂不提交**
等 Task 6 完成、Build 通过后一起提交。
@@ -785,7 +785,7 @@ actor AIRuntime {
具体路径在 App 启动时打印,见 Step 5。
- [ ] **Step 1:在终端下载模型(脚本一次性)**
- [x] **Step 1:在终端下载模型(脚本一次性)**
```bash
mkdir -p ~/tiji-models && cd ~/tiji-models
@@ -796,7 +796,7 @@ huggingface-cli download mlx-community/Qwen3-1.7B-MLX-4bit \
Expected:目录里有 `config.json` / `model.safetensors` / `tokenizer.json` 等。
- [ ] **Step 2:写 LLMSession 实现**
- [x] **Step 2:写 LLMSession 实现**
创建 `康康/AI/LLMSession.swift`:
@@ -866,11 +866,11 @@ actor LLMSession {
> **注**:`MLXLMCommon` 的具体 API 版本可能在 GenerateParameters/stream 处略有差异。如果 Step 4 编译报错,查看 mlx-swift-examples 仓库 `Libraries/MLXLLM` 的最新示例,以仓库示例为准小幅调整。
- [ ] **Step 3:把 LLMSession.swift 加入 康康 target**
- [x] **Step 3:把 LLMSession.swift 加入 康康 target**
拖入 AI group,确认 Target Membership。
- [ ] **Step 4:Build,期望成功**
- [x] **Step 4:Build,期望成功**
⌘B。
@@ -878,7 +878,7 @@ Expected:Build Succeeded。
若 MLX API 签名不匹配,参考 https://github.com/ml-explore/mlx-swift-examples 中 `Libraries/MLXLLM` 的最新 LLM 示例修正。
- [ ] **Step 5:在 KangkangApp 启动时打印沙盒路径(临时调试)**
- [x] **Step 5:在 KangkangApp 启动时打印沙盒路径(临时调试)**
打开 `康康/App/KangkangApp.swift`,在 `WindowGroup { RootView() }` 内加一个 `.onAppear`:
@@ -901,7 +901,7 @@ Expected:Build Succeeded。
📁 App Support: /Users/.../data/Containers/Data/Application/<UUID>/Library/Application Support
```
- [ ] **Step 6:把模型拷到沙盒**
- [x] **Step 6:把模型拷到沙盒**
```bash
APP_SUPPORT="<上面控制台打印的路径>"
@@ -909,7 +909,7 @@ mkdir -p "$APP_SUPPORT/Models"
cp -R ~/tiji-models/Qwen3-1.7B-MLX-4bit "$APP_SUPPORT/Models/"
```
- [ ] **Step 7:提交(本任务 + Task 5 一起)**
- [x] **Step 7:提交(本任务 + Task 5 一起)**
```bash
git add 康康/AI/ 康康/App/KangkangApp.swift 康康.xcodeproj
@@ -924,7 +924,7 @@ git commit -m "feat(ai): add AIRuntime actor and LLMSession with MLX Qwen3-1.7B"
- Create: `康康/Debug/DebugAIRunner.swift`
- Modify: `康康/Features/Me/MeView.swift`
- [ ] **Step 1:创建 DebugAIRunner**
- [x] **Step 1:创建 DebugAIRunner**
`康康/Debug/DebugAIRunner.swift`:
@@ -998,7 +998,7 @@ struct DebugAIRunner: View {
#endif
```
- [ ] **Step 2:在 MeView 末尾挂上(仅 DEBUG)**
- [x] **Step 2:在 MeView 末尾挂上(仅 DEBUG)**
打开 `康康/Features/Me/MeView.swift`,把现有内容整体替换为:
@@ -1025,15 +1025,15 @@ struct MeView: View {
#Preview { MeView() }
```
- [ ] **Step 3:在 Xcode 中加入文件**
- [x] **Step 3:在 Xcode 中加入文件**
右键 `康康/` → New Group "Debug" → 拖入 DebugAIRunner.swift,勾 "康康" target。
- [ ] **Step 4:Build,确认 OK**
- [x] **Step 4:Build,确认 OK**
⌘B → Expected: Build Succeeded。
- [ ] **Step 5:提交**
- [x] **Step 5:提交**
```bash
git add 康康/Debug/ 康康/Features/Me/MeView.swift 康康.xcodeproj
@@ -1094,7 +1094,7 @@ git commit --allow-empty -m "milestone: W2 LLM 自检通过 (simulator)"
**Files:**
- Create: `康康Tests/ModelsSchemaTests.swift`
- [ ] **Step 1:写 schema 烟测**
- [x] **Step 1:写 schema 烟测**
```swift
import Testing
@@ -1179,7 +1179,7 @@ struct ModelsSchemaTests {
}
```
- [ ] **Step 2:加入 康康Tests target,跑测试**
- [x] **Step 2:加入 康康Tests target,跑测试**
⌘U。
@@ -1187,7 +1187,7 @@ Expected:3 个测试全 pass。
若 cascade 删除测试失败 → 检查 `Indicator.report` 反向关系是否声明正确(参考 Task 2)。
- [ ] **Step 3:提交**
- [x] **Step 3:提交**
```bash
git add 康康Tests/ModelsSchemaTests.swift 康康.xcodeproj

View File

@@ -0,0 +1,42 @@
# W2 Retro · 2026-05-31
> 范围:2026-05-19(W2 起)→ 2026-05-25(W2 中段写,W3 周一前回看修订)。本次 retro 在 W2 中段写,主要是周末批量收尾的留痕。
## Status
| 风险/里程碑 | 状态 | 备注 |
|---|---|---|
| R1 · MLX 跑通 | ⚠️ 部分通过 | LLMSession.load 通过 Swift Testing 烟测,真实 tok/s 待用户手动 DebugAIRunner 验证 |
| R4 · Schema 迁移 | ✅ 通过 | 5 + 1(Symptom)个 @Model,3 + 2 个关系烟测全绿 |
| 本周里程碑 · AI 基座骨架 | ✅ | AIRuntime / LLMSession / ModelStore / FileVault 全部交付,build 干净 0 warning |
## 速度基线
- 模拟器(iPhone 17 Sim, Apple Silicon Mac):**TBD**(W3 周一前由 xuhuayong 在 macOS Designed for iPad 内点 DebugAIRunner 填入)
- 真机 iPhone 15+:**待 W3 验证**(本周未连真机,模型只 sideload 到 macOS sandbox)
> 验收门槛:模拟器 < 5 tok/s 触发 R1 红线(换 llama.cpp,W2 plan revert)。当前烟测路径无法测速,需 manual。
## 计划外完成
- **Symptom 模块**:新增 @Model + Start/End sheets + OngoingSymptomsCard。这是 CLAUDE.md §10 红线 #6 "新功能必须问'清单里有吗'" 的例外,由产品负责人决定加入。
- **Timeline 统一时间线**:TimelineEntry + TimelineRow + DateSection + TimelineGrouping,被 HomeView 和 ArchiveListView 共享。
- **ArchiveListView 提前打底**(原计划 W4):接 @Query 拉 Indicator/Report/Diary/Symptom,filter chips + 年/月分组 + 空态。
- **AppIcon**:Light/Dark/Tinted 三套 9 sizes + SVG 源。
- **Swift 6 并发清扫**:`SWIFT_DEFAULT_ACTOR_ISOLATION = MainActor` 下,把 ModelStore / FileVault / ModelKind 显式标 nonisolated,LLMSession 用 task-scoped Device.withDefaultDevice 替代 deprecated API。
## 计划内缺口
- **Task 8 Step 1-2 自检与速度基线**:延后到用户 manual 验证。
- **Task 8 Step 3 真机连测**:延后到 W3。
- **Task 10 Step 2 §8 状态更新**:已在本 retro commit 内一起完成。
## 学到的
1. **`SWIFT_DEFAULT_ACTOR_ISOLATION = MainActor` 会把跨边界类型/方法都默认推到 MainActor**,跟 actor (如 AIRuntime) 互操作时必须显式 `nonisolated` 整条调用链。`@unchecked Sendable` 不自动解锁实例方法的 isolation。
2. **iOS Simulator app sandbox 阻止读 Mac 用户目录**,集成测试无法直接验证真实推理;Mac Designed for iPad 又卡 code signing。W3 把 LLM 接口拆 SPM target 后才能写 host-fs 集成测试。
3. **`Device.withDefaultDevice` 是 TaskLocal,跨 actor 传递正常**,但跨 Task(如 AsyncStream 的 detached Task)需要在 inner Task 内重新 `withDefaultDevice`
4. **MLX Swift API 比 mlx-swift-examples 文档稳定**,真正卡的是 Swift 6 并发系统,不是 MLX 本身。
## 下周(W3)前置准备
- [ ] 用户在 macOS App 内点 DebugAIRunner,把实际 tok/s 填进本 retro 的"速度基线"段
- [ ] 准备 510 张真实化验单照片(W4 VL 回归测用),放进 ~/tiji-models/test-reports/
- [ ] 准备 20 条危险问句(W3 末医疗话术安全测试)
- [ ] 决定是否把 LLM 接口拆 SPM target(便于真实推理集成测试)
- [ ] W3 plan 周一动笔,把 Symptom + Timeline 写进 spec