依 R5 五輪決策把 visionA-local 從「Wails 內嵌 Next.js」重構為「Wails
本機伺服器控制台 + 瀏覽器 Web UI」模式(類比 Docker Desktop / Ollama)。
程式碼變動
- M8-1 砍 yt-dlp 全套(後端 resolver / URL handler / 前端 URL tab /
Makefile vendor / installer / bootstrap / CI workflow,-555 行)
- M8-2 砍 Mock 模式全套(driver/mock、mock_camera、Settings runtimeMode、
VISIONA_MOCK 環境變數,-528 行)
- M8-3 ffmpeg 從 GPL 切換到 LGPL 混合方案:Windows/Linux 用 BtbN 現成
LGPL binary,macOS 自 build minimal decoder-only 進 git
(vendor/ffmpeg/macos/ffmpeg 5.7MB + ffprobe 5.6MB,比 GPL 版省 85% 空間)
- M8-4 Wails Server Controller:state machine、log ring buffer 2000 行、
preferences.json atomic write、boot-id、Gin SkipPaths、shutdown 7+1 秒、
notify_*.go 三平台 OS 通知、watchServer 改 Error state 不 os.Exit
- M8-4b 啟動階段管線 R5-E:6 階段進度 event、20s soft / 60s hard timeout、
stage 5/6 skip 規則、sentinel file、RestartStartupSequence 5 步驟
- M8-5 Wails 控制台 vanilla HTML/JS/CSS(9 檔 ~2012 行)取代 M7-B splash:
state 視覺、log panel、startup progress panel、Stage 6 manual CTA
pulse、shutdown modal、Settings、Dark Mode、i18n 中英雙語
- M8-6 上傳影片副檔名擴充(mp4/avi/mov/mpeg/mpg)
- M8-7 Web UI Server Offline Overlay(role=alertdialog + focus trap +
wsEverConnected 容錯 + Page Visibility)
- M8-8 CORS middleware(127.0.0.1/localhost only + suffix attack 防護)+
ws/origin.go 獨立 WebSocket CheckOrigin 避 package cycle
- MAJ-4 server:shutdown-imminent WebSocket broadcast 機制
(/ws/system endpoint + notifyShutdownImminent helper)
- M8-9 Boot-ID + 瀏覽器 tab 自動重連(sessionStorage loop guard)
品質
- ~105+ 新 unit test + race detector (-count=2) 全綠
- 10 個 milestone 全部通過 Reviewer 審查
- 三方 v2 + v2.1 文件(PRD / Design Spec / TDD)+ 交叉互審紀錄
收錄在 .autoflow/
交付前待處理(M8-10)
- 重跑 make payload-macos 把舊 GPL 77MB binary 換成新 LGPL
- 三平台 end-to-end build 驗證
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
516 lines
37 KiB
Markdown
516 lines
37 KiB
Markdown
# PM 交叉審閱 TDD v2(2026-04-14)
|
||
|
||
> 審閱者:PM Agent
|
||
> 被審物:`/Users/jimchen/visionA/local-tool/.autoflow/04-architecture/TDD-v2.md` + `v2/` 全 8 份子檔
|
||
> 對照基準:`PRD-v2.md`(484 行)+ R5 五輪決策 + R5-D 補充決策
|
||
> 審閱範圍:需求面完整度、R5 / R5-D 落地、PM 5 懸念的技術回答、體驗紅線
|
||
|
||
---
|
||
|
||
## 摘要
|
||
|
||
- **總結論:需小改後通過**(不是大改、不阻擋整體方向)
|
||
- **問題數**:Major 4 個 / Minor 5 個
|
||
- **是否阻擋進開發**:**部分阻擋**——Major #1 / #2(R5-D1 / R5-D2 / R5-D3 未落地)必須在進 M8-4/M8-5 開發前補上;其餘 Major 可在 M8-10 前修正
|
||
- **整體品質**:TDD v2 對 R5 主決策的落地完整度 **90%**;對 R5-D 補充決策的落地完整度 **0%**(這是 PM 的主要不滿);對 R5 主決策以外的技術設計(state machine、LogBuffer、CORS、ffmpeg LGPL)品質很高
|
||
|
||
---
|
||
|
||
## A. 需求完整性檢查(對 PRD v2)
|
||
|
||
| PRD v2 章節 | 要求 | TDD v2 落地位置 | 檢查結果 |
|
||
|------------|------|---------------|---------|
|
||
| §4.1 保留功能 (8 項) | device scan / 模型管理 / 批次影像上傳 / camera / 上傳影片 / MJPEG / WebSocket / 系統 API / i18n / Dark Mode / Log 持久化 | `code-reuse-v2.md`(沿用 85-95%)+ `deletions.md` §1-6 明確區分保留 vs 刪除 | ✅ 無誤刪。`deletions.md` §2.6/2.7 小心處理 manager_test / api_e2e_test |
|
||
| §4.2 砍除 9 項 | URL endpoint / url_handler / yt-dlp / 前端 URL tab / Mock 全部 / webview 載 Next.js / tray / first-run 模式選擇 / US-2 Mock | `deletions.md` §1-6 + `milestone-plan.md` M8-1/M8-2 逐檔逐行 | ✅ 全部對應;`deletions.md` §8 的驗收 grep 是漂亮的防呆 |
|
||
| §4.3 新增 6 項 (N1-N6) | Wails 控制台 / 自動開瀏覽器 / Offline Overlay / boot-id / CORS / watchServer Error state | `control-panel.md`(N1 / N2)+ `web-ui-offline-overlay.md`(N3)+ `server-lifecycle.md` §9(N4)+ `cors-security.md`(N5)+ `control-panel.md` §4.7(N6) | ✅ 六項皆有專屬子檔 |
|
||
| §5 US-1 ~ US-7 AC | 每個 AC 應有技術對應 | 見 §C「US AC 追蹤表」 | ⚠️ 有 2 個 AC 沒對應(見 C)|
|
||
| §6 非功能 | 安裝檔 ≤ 185/165/165 MB、idle RAM ≤ 450 MB、首次推論、瀏覽器相容 | `milestone-plan.md` M8-10 有驗收 installer size;idle RAM 和首次推論時間**完全沒被估算** | ❌ **見 Major #3** |
|
||
| §7 第三方授權 | ffmpeg LGPL 方案 B(三平台)+ ffprobe + macOS commit + 授權檔 | `ffmpeg-lgpl.md` 完整落地 | ✅ 很完整,甚至連 `spctl` / `codesign` / BUILD.md reproducibility 都想到 |
|
||
| §8 新風險 N-R1/2/3/4 | 瀏覽器相容性 / macOS ffmpeg 維護 / 關窗誤解 / CI 測試分層 | TDD v2 §3 新增 R-v2-1 ~ R-v2-7 對應(N-R1 吸收進 R-v2-6,N-R2 吸收進 R-v2-2,N-R3 部分吸收進 AC-7.6 要求);N-R4 無明確對應 | ⚠️ **見 Minor #1**(N-R4 要求在 TDD v2 討論階段提出分層方案,TDD 只在 PM 懸念欄說「交 Testing」而沒給方案)|
|
||
|
||
---
|
||
|
||
## B. R5 / R5-D 決策落地檢查
|
||
|
||
### B.1 R5 主決策(12 條)
|
||
|
||
| 決策 | TDD v2 落地位置 | 落地狀況 |
|
||
|------|---------------|---------|
|
||
| R5-1(動機 A+B+G,純 127.0.0.1)| `control-panel.md` §1-2、`server-lifecycle.md` §2.1、`cors-security.md` §3 | ✅ 完整 |
|
||
| R5-2(視窗關閉 = 結束 server)| `server-lifecycle.md` §2.3、§7 `OnBeforeClose` return false、§8 shutdown 順序 | ✅ 完整,且 main.go 明確寫出 OnBeforeClose hook,預留未來改動空間 |
|
||
| R5-3(維持砍 tray)| `server-lifecycle.md` §2.3 註解「不 hide-to-tray」、`TDD-v2.md` §0.1 | ✅ 不存在任何 tray code |
|
||
| R5-4(啟動自動開瀏覽器 + Settings 可關)| `control-panel.md` §4.6 ServerController.Start 末段、`server-lifecycle.md` §2.1 t=2.500 | ⚠️ **語意違反 R5-D3**(見 Major #1)|
|
||
| R5-5(控制台 scope)| `control-panel.md` §1 明列「不做業務 UI」 | ✅ 完整 |
|
||
| R5-5a(Mock 完全砍)| `deletions.md` §2 + §3 + §6、`milestone-plan.md` M8-2 | ✅ 涵蓋 Go / Wails app / 前端 / i18n / CLI flag 所有層 |
|
||
| R5-6(LGPL 方案 B)| `ffmpeg-lgpl.md` §1 表格 | ✅ |
|
||
| R5-6a(macOS decoder-only ~20 MB)| `ffmpeg-lgpl.md` §2.3 configure flags + §2.2 feature set | ✅ 驗收條件 `< 25 MB` 合理 |
|
||
| R5-6b(macOS binary commit)| `ffmpeg-lgpl.md` §5 layout + §6 `.gitignore` | ✅ 連 `!/vendor/ffmpeg/macos/**` 的 gitignore 順序陷阱都提到 |
|
||
| R5-6c(一起包 ffprobe)| `ffmpeg-lgpl.md` §2.2 末段 + §2.3 cp 指令 + §7 payload + §8 installer | ✅ 三平台全覆蓋 |
|
||
| R5-7(M7 Windows 先不管)| `milestone-plan.md` M8-10 註解「R5-7 同意先不管,這次順帶驗證」 | ✅ 沒偷跑 M7 Windows,但又不漏驗 |
|
||
| 共識 #14(boot-id)| `server-lifecycle.md` §9 + `web-ui-offline-overlay.md` | ✅ 端到端 |
|
||
|
||
### B.2 R5-D 補充決策(3 條)— **這是最大問題**
|
||
|
||
| 決策 | 應落地位置 | 落地狀況 |
|
||
|------|----------|---------|
|
||
| R5-D1:Server 崩潰時保留 OS 原生通知 | `control-panel.md` §4.7 watchServer Error state / `server-lifecycle.md` §6 watchServer 或 `control-panel.md` §4 ServerController.setState | ❌ **完全缺失**。`grep -ri 'osascript|notify-send|powershell.*notification' v2/` 零匹配。TDD 只 emit `server:error` Wails event 給控制台,沒有發系統原生通知 |
|
||
| R5-D2:Linux 預設 `openBrowserOnStart=OFF`,mac/Win = ON | `preferences.go`(預設值載入邏輯)或 `control-panel.md` §4.3 Preferences struct 預設 | ❌ **完全缺失**。TDD 只定義 `Preferences.OpenBrowserOnStart bool`,沒有 `NewDefaultPreferences()` 依 `runtime.GOOS` 分平台初值的邏輯。若 `preferences.json` 不存在,使用者第一次啟動在 Linux 上會用 Go zero value(false),剛好誤中,但這是 **碰巧對**,不是設計對 |
|
||
| R5-D3:每次 Start 成功都自動開瀏覽器(不只首次)| `control-panel.md` §4.6 ServerController.Start 末段 | ❌ **實作與決策相反**。現況是 `if c.app.prefs.OpenBrowserOnStart && !c.app.autoOpenedThisSession` — `autoOpenedThisSession` per-session-once 只會開一次,Restart 後不再開。**這和 R5-D3 「每次啟動都自動開」語意相反**。milestone-plan.md M8-9 step 3 甚至把「防呆:autoOpenedThisSession 確保 Restart 不會二次開瀏覽器」列為驗收條件,直接落地了**錯的語意** |
|
||
|
||
---
|
||
|
||
## C. US AC 追蹤表(PRD §5 × TDD v2)
|
||
|
||
| AC | 需求 | TDD 對應 | 狀況 |
|
||
|----|------|---------|------|
|
||
| AC-1.1/1.2 | 安裝 ≤ 3 分 / 上限 5 分 | v1 沿用,TDD 未碰 | ✅ |
|
||
| **AC-1.3** | 首次啟動 Wails + server + 瀏覽器 ≤ 10 秒 | **未估算** | ❌ Major #3 |
|
||
| AC-1.4 | 無硬體時顯示 empty state | `deletions.md` §6.5 noDevices 文案、R-v2-7 | ✅ |
|
||
| AC-1.5 | Gatekeeper 引導 | 同 v1 沿用 | ✅ |
|
||
| AC-1.6 | 可關閉自動開瀏覽器 | Preferences.OpenBrowserOnStart | ✅(但受 R5-D2 / D3 牽連)|
|
||
| AC-2.1 | 日常啟動 ≤ 5 秒全就緒 | 未估算 | ⚠️ Minor #2 |
|
||
| AC-2.2 | 保留 i18n / dark mode 偏好 | 前端 localStorage 沿用 | ✅(但沒寫進 TDD) |
|
||
| AC-2.3 | 關過自動開瀏覽器後只開控制台 | Preferences + autoOpenedThisSession | ✅ |
|
||
| AC-3.7 | 使用者關瀏覽器後不發 toast / 不發 OS 通知 | TDD 未明確處理,`control-panel.md` 沒有 toast 入口,默認符合 | ⚠️ 和 R5-D1 衝突。AC-3.7 說「不發 OS 通知」,R5-D1 說「server 崩潰時保留 OS 通知」。**兩者 scope 不同**(AC-3.7 是裝置事件,R5-D1 是 server 崩潰),TDD 都沒處理,但兩者需要分開實作 |
|
||
| AC-4.5 | 拖放 .nef 只在瀏覽器有效 | `control-panel.md` §1 明列「不做業務」隱含 | ✅ |
|
||
| AC-4.6 | 8 個預設 .nef + 無 Model Zoo | v1 沿用 | ✅ |
|
||
| AC-5.1-5.6 | 瀏覽器 Workspace / 後端 camera pipeline / 不走 getUserMedia | v1 沿用 | ✅ |
|
||
| AC-6.1-6.4 | 5 種副檔名上傳影片 / 不支援 URL | `milestone-plan.md` M8-6 + `deletions.md` §5.1 前端 accept + backend handler patch | ✅ diff 清楚 |
|
||
| **AC-7.1** | 偵測斷線顯示 Offline Overlay | `web-ui-offline-overlay.md` § 全部 | ✅ |
|
||
| **AC-7.2** | Overlay 文案 + Retry | `web-ui-offline-overlay.md` §4.6 i18n | ✅ |
|
||
| **AC-7.3** | 控制台狀態變 Error + log panel 顯示錯誤 | `control-panel.md` §4.7 + §3 狀態 badge 紅 | ✅ |
|
||
| **AC-7.4** | Restart 後 boot-id 變 → reload | `server-lifecycle.md` §2.2 + `web-ui-offline-overlay.md` §4.3 | ✅ |
|
||
| AC-7.5 | 關 Wails → server 結束 → 相同 Overlay | `server-lifecycle.md` §2.3 | ✅ |
|
||
| AC-7.6 | Close 按鈕 tooltip / confirm 警語 | **未落地**(Design Agent 任務,TDD 未強制 Wails main.go 加 confirm) | ⚠️ Minor #3 |
|
||
|
||
---
|
||
|
||
## D. PM 5 懸念的技術回答檢查
|
||
|
||
| # | PRD §11 問題 | 歸屬 | TDD 有無回答 |
|
||
|---|-------------|------|------------|
|
||
| 1 | 自動開瀏覽器 Settings 與 Web UI Settings 資料是否同一份 | Architect | ✅ `control-panel.md` §4.1 明確說「`visiona-local/preferences.go` 存 `<dataDir>/preferences.json`」— 這是**控制台側**的偏好;Web UI 側的 i18n / dark mode 仍用 browser localStorage(v1 沿用)。兩邊**分離**,PM 接受這個答案 |
|
||
| 2 | AC-1.3 10 秒預算是否可達 | Architect 估算 | ❌ **完全沒估算**。TDD 沒有任何段落討論「Wails 冷啟動 + Go server waitHealthy + 瀏覽器冷啟動」的時間預算分解。PRD 明確要求 Architect 驗證此預算 |
|
||
| 3 | idle RAM ≤ 450 MB 可達性 | Architect 實測 | ❌ **完全沒提**。TDD 沒有任何段落討論 idle RAM 估算(v2 砍了 Mock state 和 yt-dlp 子程序後的 baseline) |
|
||
| 4 | N-R4 CI / E2E 測試分層方案 | Testing Agent(現階段不審) | — 略過 |
|
||
| 5 | 「由 visionA-local 跑」常駐徽章 | Design Agent(現階段不審) | — 略過 |
|
||
|
||
→ **PM 5 懸念中的 2/3(AC-1.3、idle RAM)完全沒被回答。這兩題是 PRD §11 明列「交 Architect」的題目,Architect 沒回等於把球踢回給 PM。**
|
||
|
||
---
|
||
|
||
## E. 臆測 / 誤刪 / 誤解清單
|
||
|
||
### E.1 臆測超出範圍
|
||
|
||
- **無**。TDD v2 沒有偷偷新增 LAN mode、tray fallback、daemon、auto-update、telemetry 等 R5 明確否決的功能。這點乾淨
|
||
|
||
### E.2 誤刪
|
||
|
||
- **無誤刪**。批次影像上傳(R5 共識 10 / PRD §4.1)在 `deletions.md` 中未被列入刪除清單,`code-reuse-v2.md` 也把它放在沿用區。`camera_handler.go` 只砍 `StartFromURL`,沒碰 `UploadBatch` 相關 handler
|
||
- **但有一個 risky 邊界**:`deletions.md` §1.1 說「若 `NewVideoSourceFromURL` **唯一呼叫者**是 StartFromURL → 一起砍」。實際 grep 顯示 `camera_handler.go:731` 還有 `NewVideoSourceFromURLWithSeek` 被本地上傳影片 seek 呼叫。**M8-1 執行者必須先把這個 grep 結果列出**,否則可能會誤砍本地影片 seek 功能。TDD `ffmpeg-lgpl.md` §11 已警示這點,但 `deletions.md` §1.1 的敘述「可從 RTSP stream 呼叫」不精準,實際是「從本地影片 seek 呼叫」
|
||
|
||
### E.3 誤解 R5 / R5-D
|
||
|
||
- **R5-4 理解錯誤**(**Major #1**):TDD 把「首次啟動自動開瀏覽器」實作成 per-session-once(`autoOpenedThisSession` flag),但 R5-D3 明確修正語意為「每次 Start Server 成功都開」。這個誤解在 `milestone-plan.md` M8-9 和 `control-panel.md` §4.6 各出現一次,是**系統性誤解**
|
||
- **R5-D1 / R5-D2 整組遺漏**(**Major #1** 包含)
|
||
- **R5-2 理解正確**:TDD 明確寫 `OnBeforeClose` return false、不 hide-to-tray、有 `shutdownGracePeriod` 5s → 10s 對齊 server 的討論。這點很到位
|
||
|
||
---
|
||
|
||
## F. 體驗紅線檢查
|
||
|
||
### F.1 北極星指標「5 分鐘內瀏覽器跑第一幀推論」可達嗎
|
||
|
||
PRD §1.4 把「瀏覽器啟動時間」也納入 5 分鐘預算。TDD 沒有做端到端預算分解,但從元件看:
|
||
|
||
- Wails 冷啟動:~0.5-1 s(v1 經驗)
|
||
- Go server spawn + waitHealthy:1-3 s(依 Python sidecar 啟動)
|
||
- `OpenInBrowser("")`:系統 call ~0.1 s
|
||
- 瀏覽器冷啟動(第一次點開 Chrome/Safari/Edge):**3-8 s**(最大不確定性)
|
||
- Next.js static export 載入:~0.5-1 s
|
||
|
||
→ 在已開瀏覽器的情境下 5-6 s 可達 AC-2.1 的「日常啟動 ≤ 5 秒」;在冷啟動情境下 6-13 s **很可能超過 AC-1.3 的 10 秒上限**。
|
||
|
||
**PM 結論**:AC-1.3 的 10 秒上限在「使用者尚未開過瀏覽器」的情境下可能不可達。建議 Architect 在 TDD v2 增補一段預算分解,若確實超過,PRD 要放寬到 15 秒。這是進開發前必須釐清的事項(見 Major #3)。
|
||
|
||
### F.2 race condition:Wails 關閉視窗 → StopServer 過程中瀏覽器 tab
|
||
|
||
TDD v2 `R-v2-4` 已明確提到這個 race condition(0.5-5s 內瀏覽器可能看到 ECONNREFUSED 但 Overlay 還沒觸發),並分析為「實務上使用者關 Wails 通常也會關瀏覽器 tab,race 不構成實際問題;若真的沒關,15 s 後 Overlay 會出現」。**PM 認可這個取捨**,但有補充意見:
|
||
|
||
- **建議**:在 Wails OnBeforeClose 觸發前,先透過 WebSocket broadcaster 發一個 `server:shutdown-imminent` 訊息給所有連著的瀏覽器 tab(利用現有 `/ws/server-logs` 或 `/ws/devices/*` 通道),前端收到後立即顯示 Overlay,不需要等 15 s polling。這個成本低(server 端只 3-5 行、前端 store 新增一個 listener 即 10 行)
|
||
- 若 Architect 評估成本過高 → 接受現狀。這是 **Minor #4**,不阻擋進開發
|
||
|
||
### F.3 「關閉視窗 ≠ 結束 server」的心智負擔(N-R3)
|
||
|
||
PRD §8.2 N-R3 要求三處文案到位:
|
||
1. Wails Close 按鈕 tooltip / confirm dialog(AC-7.6)— **TDD 未落地**(main.go 只寫了 `OnBeforeClose: return false`,沒加 confirm 對話框邏輯;是否加是 Design Agent 的 scope,但 TDD 應該**預留 hook**)
|
||
2. Offline Overlay 文案引導 — `web-ui-offline-overlay.md` §4.6 ✅
|
||
3. First-Run 歡迎頁一句說明 — TDD 未提(但 v1 有,v2 沿用,推測 OK)
|
||
|
||
→ **Minor #3**:TDD 應在 `server-lifecycle.md` §7 的 main.go 範例裡保留 `OnBeforeClose` 可讀 `Preferences.ConfirmOnClose` 的 hook,而不是寫死 `return false`。這樣 Design Agent 後續加 confirm dialog 時不用再重寫 TDD
|
||
|
||
---
|
||
|
||
## G. PM 對 Architect Q4 的回答
|
||
|
||
Architect 在 `server-lifecycle.md` §10 問:「`shutdownGracePeriod` 5s → 10s 的副作用?使用者頻繁開關 app 可能感覺變慢。」
|
||
|
||
**PM 回答**(基於使用者體驗基準):
|
||
|
||
| 問題 | 答案 |
|
||
|------|-----|
|
||
| 總 grace period 建議值 | **7 秒**(而非 10 秒),理由:Nielsen Norman 的研究指出 1 秒內 = 即時反饋、10 秒 = 使用者注意力臨界點;7 秒保留足夠 server 優雅結束餘量,又不會逼近「我是不是當機」的感受閾值 |
|
||
| 超過多久要顯示「停止中…」進度提示 | **1 秒**。Wails 收到關閉訊號後應立即(< 100 ms)顯示一個不可關閉的 modal「正在停止本機伺服器…」+ spinner,避免使用者看到「空白卡住」。這個 modal 在 7 秒後若 server 仍未退出,換文字為「伺服器未正常結束,正在強制關閉…」,之後 SIGKILL |
|
||
| 如果 7 秒真的不夠(server 端某些清理需要 > 7 秒)| 兩個選擇:(a) 降低 server 端清理時間(理想)(b) 把 server 端的 `shutdownFn` 10 s timeout 改為 6 s,與 Wails 7 s 留 1 s 緩衝 |
|
||
|
||
**建議**:採用 PM 的 7 秒 + modal 方案,這是比 Architect 提的「5-10 秒區間 emit event」更簡單(不需要 event 機制)且更符合 UX 基準的做法。Architect 若有技術面反對意見,請在使用者確認前提出。
|
||
|
||
---
|
||
|
||
## H. 問題清單
|
||
|
||
### Major(阻擋進開發,必須在 M8-4 / M8-5 開發前修)
|
||
|
||
#### Major #1:R5-D1 / R5-D2 / R5-D3 三條補充決策完全沒落地
|
||
|
||
**問題**:R5-D 三條是使用者在 2026-04-14 Design v2 產出後的補充決策,時間上**晚於 TDD v2 draft**,但 TDD v2 必須被修正以落地這三條。現況:
|
||
|
||
- **R5-D1(崩潰時保留 OS 原生通知)**:TDD 只在 Wails 控制台發 `server:error` event,沒叫 `osascript display notification` / `powershell New-BurntToastNotification` / `notify-send`
|
||
- **R5-D2(Linux 預設自動開瀏覽器 OFF)**:`preferences.go` 沒有依 `runtime.GOOS` 分平台 default 的邏輯
|
||
- **R5-D3(每次 Start 都開,不只首次)**:`autoOpenedThisSession` flag 和 milestone-plan M8-9 驗收條件明確是 per-session-once,**和決策相反**
|
||
|
||
**應加在**:
|
||
|
||
| 決策 | 子檔 | 修正位置 |
|
||
|------|------|---------|
|
||
| R5-D1 | `control-panel.md` §4.7(watchServer Error state)+ 新增 §4.8「OS 原生通知 helper」 | 新增 `visiona-local/notify_darwin.go` / `notify_windows.go` / `notify_linux.go` 三檔,在 `ctrl.setState(ServerStateError, ...)` 時呼叫 |
|
||
| R5-D2 | `control-panel.md` §4.3 Preferences 型別 + `v2/server-lifecycle.md` §2.1 載入流程 | 新增 `NewDefaultPreferences() Preferences`:`runtime.GOOS == "linux"` → `OpenBrowserOnStart: false`,否則 `true`。`preferences.go` 的 load 若檔不存在,用這個預設 |
|
||
| R5-D3 | `control-panel.md` §4.6 `ServerController.Start` 末段 + `milestone-plan.md` M8-9 step 3/5 驗收條件 | **砍 `autoOpenedThisSession` flag**,改為:只要 `prefs.OpenBrowserOnStart == true` 就每次 Start 成功後 `OpenInBrowser("")`。M8-9 驗收改為「多次按 Restart:每次都開新 tab(或 focus 既有 tab,瀏覽器行為決定)」 |
|
||
|
||
**阻擋等級**:Major,必須在 M8-4/M8-5 開發前修 TDD 子檔。因為這三條會影響 `preferences.go` / `server_control.go` / 新增的 notify_*.go 的介面簽名,若先開發再改會浪費時間
|
||
|
||
---
|
||
|
||
#### Major #2:R5-D3 誤解已被寫進 milestone-plan M8-9 驗收條件
|
||
|
||
**問題**:`milestone-plan.md` 第 335 / 347 / 350 行把「Restart 不會二次開瀏覽器」列為**驗收成功**條件。這會在 Reviewer 審查 M8-9 時被當成「通過」,但其實這**違反 R5-D3**。
|
||
|
||
**修正**:M8-9 step 3 改寫,驗收條件改為:
|
||
- 首次啟動 Server:瀏覽器開 1 個 tab
|
||
- 手動按 Restart:瀏覽器開第 2 個 tab(或在瀏覽器自動重用既有 tab,視瀏覽器行為)
|
||
- 使用者在 Preferences 關掉 `openBrowserOnStart`:之後 Start/Restart 都不開瀏覽器
|
||
|
||
**阻擋等級**:與 Major #1 綁在一起修
|
||
|
||
---
|
||
|
||
#### Major #3:PM 5 懸念中 AC-1.3(10 秒)與 idle RAM(≤ 450 MB)沒被 Architect 回答
|
||
|
||
**問題**:PRD §11 明確把這兩題交給 Architect「TDD v2 驗證」,但 TDD 所有 8 個子檔都找不到任何段落在討論這兩個預算。
|
||
|
||
**應加在**:
|
||
|
||
- **AC-1.3 10 秒可達性**:建議在 `TDD-v2.md` §1 或 `server-lifecycle.md` §2.1 後新增一節「冷啟動預算分解」:
|
||
```
|
||
Wails 冷啟動 ~0.5-1 s
|
||
migrateOldDataDirs ~0.01 s
|
||
startIPCServer ~0.05 s
|
||
seedUserDataDir ~0.02-2 s(首次安裝時 copy 8 個 .nef,約 73 MB)
|
||
ensurePythonRuntime ~0.5-3 s(首次啟動 bundled Python extract)
|
||
exec.Command spawn ~0.01 s
|
||
waitHealthy (Python sidecar 起) ~1-3 s
|
||
OpenInBrowser system call ~0.1 s
|
||
瀏覽器冷啟動 ~3-8 s(使用者尚未開瀏覽器)
|
||
Next.js SSG 首次載入 ~0.5-1 s
|
||
────────────────────────
|
||
首次安裝冷啟動總計 ~5.5-18 s
|
||
回訪情境(瀏覽器已開)~2.5-10 s
|
||
```
|
||
結論:**AC-1.3 的 10 秒上限在首次安裝 + 冷啟動瀏覽器情境下不可達**,建議 PRD 放寬到 15 秒,或在 PRD AC-1.3 明確註記「若瀏覽器尚未啟動,可能延長到 15 s」
|
||
|
||
- **idle RAM ≤ 450 MB**:建議在 `TDD-v2.md` §3 風險清單新增一條,或在 `control-panel.md` 新增一節「資源估算」:
|
||
```
|
||
Wails 殼 ~80-120 MB(WebView2 / WKWebView 基礎)
|
||
Go server 子程序 ~40-60 MB(Gin + 8 個預載入 .nef metadata)
|
||
Python sidecar ~150-220 MB(Python runtime + numpy + opencv + pyusb)
|
||
logs/ LogBuffer 等 ~5 MB
|
||
────────────────────
|
||
總計 ~275-405 MB
|
||
```
|
||
結論:**idle RAM ≤ 450 MB 應可達**,上限 550 MB 安全。PM 可以接受此估算作為 TDD 回答
|
||
|
||
**阻擋等級**:Major,必須在使用者確認 TDD v2 前補上。若 10 秒不可達,要決定是放寬 AC-1.3 還是改架構(例如 Wails 內建 WebView 搭配預先隱藏視窗的 fallback)
|
||
|
||
---
|
||
|
||
#### Major #4:shutdownGracePeriod 5s → 10s 的使用者體感取捨未解決
|
||
|
||
**問題**:TDD `server-lifecycle.md` §10-2 把這題列為「待確認」,但這是**體驗面**的問題,不是技術面,Architect 不該把球踢回使用者。
|
||
|
||
**PM 回答**:見 §G。採用 **7 秒 + 1 秒內顯示「停止中…」modal** 方案。
|
||
|
||
**應加在**:`server-lifecycle.md` §8 把 `shutdownGracePeriod` 改為 7 s,§5 ServerController.Stop 新增 `emit "server:stopping"` event,前端新增「stopping modal」元件(`control-panel.md` §5 action-bar.js 補),M8-5 驗收條件加「關閉視窗後 1 秒內看到停止中 modal」
|
||
|
||
**阻擋等級**:Major,但範圍小。可併入 M8-4 / M8-5 開發
|
||
|
||
---
|
||
|
||
### Minor(可先進開發,後續修)
|
||
|
||
#### Minor #1:N-R4(CI / E2E 測試分層)沒有方案
|
||
|
||
PRD §8.2 明確要求 Architect 在 TDD v2 討論階段提出分層方案。TDD 沒提。但這題現階段交 Testing Agent,暫不阻擋。
|
||
|
||
**建議**:在 `milestone-plan.md` M8-10 新增一段「測試分層建議」,或在 TDD v2 §3 風險區新增 R-v2-8 占位,標註「待 Testing Agent 審 TDD 時補方案」
|
||
|
||
---
|
||
|
||
#### Minor #2:AC-2.1(日常啟動 ≤ 5 秒)沒估算
|
||
|
||
和 Major #3 同源。若 Major #3 補上冷啟動預算分解,順便也覆蓋 AC-2.1
|
||
|
||
---
|
||
|
||
#### Minor #3:AC-7.6(Wails Close 按鈕 tooltip / confirm 警語)沒落地
|
||
|
||
TDD `server-lifecycle.md` §7 的 main.go 範例寫死 `OnBeforeClose: return false`。這等同「直接關,無警告」,和 PRD AC-7.6 要求的「明確寫『關閉此視窗會結束 Local Server』」衝突。
|
||
|
||
**建議**:TDD 應預留 hook,例如:
|
||
```go
|
||
OnBeforeClose: func(ctx context.Context) (prevent bool) {
|
||
if app.prefs.ConfirmOnClose {
|
||
// TODO: Design Agent 決定 confirm dialog 文案
|
||
// 用 wailsRuntime.MessageDialog 跳對話框
|
||
}
|
||
return false
|
||
},
|
||
```
|
||
這樣 Design Agent 後續加 dialog 時不用再修 main.go。是否顯示 confirm 由 Design Agent 決定,但 hook 要先留
|
||
|
||
---
|
||
|
||
#### Minor #4:Wails 關閉時可主動通知瀏覽器 tab 避免 15 s race condition
|
||
|
||
見 §F.2。建議在 Wails OnBeforeClose 之前透過 WebSocket 廣播 `server:shutdown-imminent` 訊息,前端立即顯示 Overlay。成本低(server 端 ~3 行、前端 ~10 行),體驗提升明顯。
|
||
|
||
**阻擋等級**:Minor,可在 M8-10 smoke test 發現體驗不佳時補
|
||
|
||
---
|
||
|
||
#### Minor #5:`deletions.md` §1.1 「RTSP stream 可能呼叫 NewVideoSourceFromURL」敘述不精準
|
||
|
||
實際 grep 顯示 `camera_handler.go:731` 用 `NewVideoSourceFromURLWithSeek` 處理**本地上傳影片的 seek**,不是 RTSP。若 M8-1 執行者照字面理解,會把「不是 URL 但共用函式」的本地影片 seek 功能誤砍。
|
||
|
||
**修正**:`deletions.md` §1.1 文字改為「若唯一呼叫者是 `StartFromURL` → 整個砍;若仍有本地影片 seek 呼叫者(camera_handler.go:731 `NewVideoSourceFromURLWithSeek`)→ **函式保留**,但 rename 去掉 `FromURL` 字眼避免誤解,例如改為 `NewVideoSourceWithSeek`,並把 `--disable-network` 這項 configure flag 保留(因為本地檔案讀取走 `protocol=file` 不需 network)」
|
||
|
||
---
|
||
|
||
## I. 通過 / 不通過 結論
|
||
|
||
### 結論:**條件通過**
|
||
|
||
| 條件 | 必須完成 |
|
||
|------|---------|
|
||
| Major #1 + Major #2 修完(R5-D1/D2/D3 落地 + milestone-plan 驗收條件修正)| **進 M8-4 / M8-5 前必須** |
|
||
| Major #3 補上冷啟動預算分解 + idle RAM 估算 | **使用者確認 TDD v2 前必須** |
|
||
| Major #4 採用 7 秒 + modal 方案 | **進 M8-4 / M8-5 前必須**(介面簽名受影響)|
|
||
| Minor #1-#5 | 可先進開發,M8-10 smoke test 前補齊即可 |
|
||
|
||
### 整體評價
|
||
|
||
**TDD v2 品質:8.5/10**(扣分在 R5-D 整組遺漏 + PM 5 懸念 2 題沒回答)。
|
||
|
||
- ✅ **值得稱讚**:
|
||
- `ffmpeg-lgpl.md` 的完整度(configure flags、BUILD.md reproducibility、spctl 驗證、gitignore 陷阱)
|
||
- `deletions.md` 的「按表操作 + 驗收 grep」設計,防呆到位
|
||
- R5-2 / R5-3 的處理乾淨俐落(不存在任何 tray 殘跡)
|
||
- state machine 五態的思考完整(`txMu` vs `mu` 雙 mutex 對抗 race)
|
||
- 新風險 R-v2-1 ~ R-v2-7 對應具體且有緩解
|
||
- milestone-plan.md 的依賴圖清晰,支援平行開發
|
||
|
||
- ❌ **需要補強**:
|
||
- R5-D 三條補充決策**整組遺漏**,這是 PM 的主要不滿。Architect 需要在使用者回答 R5-D 後,立即在 TDD v2 出一個 patch
|
||
- PM 5 懸念的 AC-1.3 與 idle RAM **完全沒回答**,PRD 明確要求 Architect 在 TDD v2 驗證
|
||
- UX 取捨(shutdownGracePeriod)被當成技術問題丟回使用者
|
||
|
||
### 下一步建議
|
||
|
||
1. Orchestrator 啟動 Architect Agent,做 **TDD v2 patch**,聚焦修 Major #1 ~ #4
|
||
2. 修完後 Orchestrator 再啟動 PM 做第二輪審閱(只審 patch 部分,~30 分鐘)
|
||
3. 同時 Orchestrator 啟動 Design Agent 做 Design 交叉審閱(本輪 PM 審閱發現 AC-7.6 是 Design 的未決項)
|
||
4. Design + PM 兩輪都通過後交使用者確認 → 進 M8-1 ~ M8-3(互不依賴的三個砍除/ffmpeg milestone 可立即啟動,不受 Major #1/#2 影響,加速開發)
|
||
|
||
---
|
||
|
||
## 附錄:對照資料
|
||
|
||
- **PRD v2**:`/Users/jimchen/visionA/local-tool/.autoflow/02-prd/PRD-v2.md`(484 行)
|
||
- **TDD v2 索引**:`/Users/jimchen/visionA/local-tool/.autoflow/04-architecture/TDD-v2.md`(136 行)
|
||
- **TDD v2 子檔 8 份**:`/Users/jimchen/visionA/local-tool/.autoflow/04-architecture/v2/`(3738 行)
|
||
- **R5 / R5-D 決策**:`/Users/jimchen/visionA/local-tool/.autoflow/progress.md` §「R5 第五輪」+ §「R5-Design 補充」
|
||
|
||
---
|
||
|
||
# PM 第二輪審閱 TDD v2.1(2026-04-14)
|
||
|
||
> 審閱者:PM Agent
|
||
> 被審物:`TDD-v2.md` v2.1 + 9 份 `v2/*.md` 子檔(新增 `startup-pipeline.md`)
|
||
> 審閱範圍:只審 v2.0 → v2.1 補丁(Major × 4 + Minor × 5 + R5-E 落地 + Architect 自補),不重審 v2.0 既有內容
|
||
|
||
## 摘要
|
||
|
||
- **總結論:通過(附 1 個 Minor 餘項)**
|
||
- 第一輪 **Major × 4 全部修好**;Minor × 5 修好 4 個 + 1 個以另一等效方案處理
|
||
- **R5-E1~E6 六條全部落地**(`v2/startup-pipeline.md` 新檔 518 行,程式碼級完整)
|
||
- **不阻擋進 M8 開發**。M8-1/M8-2/M8-3/M8-8 可立即啟動;M8-4/M8-4b/M8-5/M8-9 在 Design v2.1 文案完成後即可啟動
|
||
|
||
---
|
||
|
||
## A. 第一輪 Major 修復檢查
|
||
|
||
| # | 問題 | 檢查位置 | 結果 |
|
||
|---|-----|---------|------|
|
||
| **Major 1 R5-D1 OS 通知** | `server-lifecycle.md` 新增 §10(L690-791)含 `notify.go` + macOS `osascript` / Linux `notify-send -u critical` / Windows PowerShell BurntToast + `msg *` fallback;`control-panel.md` L482-486 在 Start 失敗時 `go sendCrashNotification(...)`;§6 diff(L405-408)在 watchServer 3 次失敗時 fire-and-forget;`startup-pipeline.md` L295-299 pipeline 失敗時一併發通知 | ✅ 完整。觸發點三處(Start 失敗 / watchServer 3 次失敗 / startup pipeline 失敗)都有 non-blocking `go` 呼叫 |
|
||
| **Major 1 R5-D2 Linux 預設 OFF** | `server-lifecycle.md` §11.3(L824-850)`DefaultPreferences()` 依 `runtime.GOOS != "linux"` 分平台;`control-panel.md` L169 + L498-500 註解對應;`milestone-plan.md` M8-9 驗收 L404-405 分 macOS/Win vs Linux 兩條 | ✅ 完整 |
|
||
| **Major 1 R5-D3 每次都開** | `control-panel.md` `ServerController.Start` L491-503 直接檢查 `if c.app.prefs.AutoOpenBrowser` + 註解明確「取消 v2.0 的 autoOpenedThisSession flag」;`server-lifecycle.md` L923 明示「已移除」;`milestone-plan.md` L393 / L411 / L439 反覆強調砍除 | ✅ 完整。但 `code-reuse-v2.md:92` 有殘留(見 §G Minor)|
|
||
| **Major 2 M8-9 驗收條件** | `milestone-plan.md` M8-9 L407「多次按 Restart:若 `AutoOpenBrowser=true`,每次 Restart 都會呼叫 `OpenInBrowser`」L411 Reviewer 檢查重點「不存在 `autoOpenedThisSession` 欄位」 | ✅ 完整。原「Restart 不會二次開」條件已全數砍除 |
|
||
| **Major 3 §11-1 Preferences 持久化** | `server-lifecycle.md` §11 全段(L795-924):§11.1 `<dataDir>/preferences.json` + §11.2 struct 定義 + §11.3 DefaultPreferences + §11.4 讀寫時機 + L879 atomic write-rename | ✅ 完整 spec |
|
||
| **Major 3 §11-2 冷啟動預算** | 被 R5-E 取代(索引 L35 明示「原 10 秒可達性已被 R5-E 60 s 上限 + 階段化進度取代」);`server-lifecycle.md` §2.1a(L83-90)補上日常啟動 ~3.8 s 估算 | ✅ 以 R5-E 取代是合理方案 |
|
||
| **Major 3 §11-3 450 MB 範圍** | `server-lifecycle.md` §11.6(L925-943)明確「450 MB 目標不含瀏覽器 tab 的記憶體消耗」+ 量測條件定義 + 悲觀情境 50 MB 超標時的 fallback | ✅ 完整 |
|
||
| **Major 4 shutdown 7+1** | `server-lifecycle.md` L125 PM Q4 定案「7 秒 + 1 秒 modal」;L527 `shutdownGracePeriod = 7 秒`;L550 `graceTimer := time.NewTimer(7 * time.Second)`;§8(L499-521)含廣播 + 1 秒 modal + SIGKILL 完整順序;§2.3 時序 t=1.000 `shutdown:modal-show` | ✅ 完整,採 PM 方案 |
|
||
|
||
**結論**:4 個 Major 全部修好。
|
||
|
||
---
|
||
|
||
## B. 第一輪 Minor 修復檢查
|
||
|
||
| # | 問題 | 檢查 | 結果 |
|
||
|---|-----|------|------|
|
||
| Minor 1 N-R4 測試分層 | `control-panel.md` L826 標 `blocked-on-testing-agent` | ✅ 按約定處理 |
|
||
| Minor 2 AC-2.1 ≤5 s | `server-lifecycle.md` §2.1a(L83-90)日常啟動 ~3.8 s 估算表 + 結論「遠低於 60 s 上限」| ✅ |
|
||
| Minor 3 OnBeforeClose confirm hook | `server-lifecycle.md` §7(L475-479)寫死 `return false` + 註解「不跳確認對話框」,**未預留 `ConfirmOnClose` hook**;但 Architect 在 L490-493 解釋「Wails v2 default 就是直接關,與 R5-2 語意一致,不加 hook」 | ⚠️ 以等效方案處理。PM 第一輪建議是「預留 hook」方便未來改動,Architect 選擇「保持乾淨不加 dead code」。兩者語意均不彈對話框,符合 R5-2;PM 接受此取捨,**非阻擋** |
|
||
| Minor 4 WebSocket shutdown-imminent | `server-lifecycle.md` §2.3 t=0.005 / §8 L511 / `web-ui-offline-overlay.md` §3.2a(L78-95)+ L158 新增 `use-shutdown-watcher.ts` + L361 訂閱處理;server 新增 `/ws/system` endpoint;Wails `ctrl.Stop()` SIGTERM 前先廣播 | ✅ 完整(server + 前端 + Wails 三側齊全)|
|
||
| Minor 5 deletions.md §1.1 精準化 | `deletions.md` L38-94 新增 v2.1 Minor 5 段落:明列 `grep -rn 'NewVideoSourceFromURL'` 命令 + `camera_handler.go:731` 是 `handleVideoSeek` 的 `videoIsURL` dead code + 整個 function 砍 + `videoIsURL` field 砍 + 驗收 grep | ✅ 完整,甚至列出驗收 grep 指令 |
|
||
|
||
**結論**:5 個 Minor 全部處理(1 個以等效方案)。
|
||
|
||
---
|
||
|
||
## C. R5-E 落地檢查(重點)
|
||
|
||
| # | 預期 | 位置 | 結果 |
|
||
|---|-----|------|------|
|
||
| **R5-E1** 60 s hard timeout | `startup-pipeline.md` L156 `startupHardTimeout = 60 * time.Second` + L325-330 watcher 檢查 `sinceTotal > startupHardTimeout` → `Fail()` | ✅ |
|
||
| **R5-E2** 6 階段化 | L154 `startupTotalStages = 6` + L167-177 `StartupPipeline` struct + §3 6 階段表(L118)+ L422 前端 render 6 rows | ✅ |
|
||
| **R5-E3** 20 s soft timeout 顯示重試提示 | L155 `startupSoftTimeout = 20 * time.Second` + L332-346 watcher 檢查 `sinceStage > startupSoftTimeout` → emit `startup:stage-timeout` + `softTimeoutEmitted` flag 防重複 | ✅ |
|
||
| **R5-E4** 60 s Error state + 三按鈕 | L325-330 hard timeout 呼叫 `Fail()` + `emitError()` + L292-294 同步叫 `ctrl.setState(ServerStateError, ...)`;三按鈕(重試/檢視 log/回報)在 `startup-panel.js` `showStartupError()`(L469+)和 Design Spec 對齊 | ✅ |
|
||
| **R5-E5** 階段文字交 Design | `startup-pipeline.md` §2(L87-105)只定義 i18n key(`startup.stage.{1-6}.label`),實際文字留給 `i18n/zh-TW.json` / `en-US.json`(Design 填);L517 明示「R5-E5 Design Spec v2.1 尚未敲定,i18n key 已預留」 | ✅ 零硬編碼文字,完全交 Design |
|
||
| **R5-E6** WebSocket 連上 = 就緒 | `startup-pipeline.md` L22 R5-E6 明文;§3 L118 階段 6 定義「`OnClientConnected` 第一次觸發」;L120-130 兩種實作方式(long-poll endpoint / sentinel file)留給 M8-4b 執行者選擇;L515 `startup-pipeline.md` 末段留 A-Q1 待 M8-4b 決定 | ✅ 設計完整,實作方式待 M8-4b 決定(PM 接受)|
|
||
|
||
**結論**:R5-E 六條全部落地,且程式碼級細節(event schema、1-indexed 陣列、非阻塞 EventsEmit、softTimeoutEmitted 防重複、watcher 生命週期管理)皆嚴謹。
|
||
|
||
---
|
||
|
||
## D. TDD ↔ PRD v2.1 對齊
|
||
|
||
| PRD v2.1 AC | TDD v2.1 對應 | 結果 |
|
||
|------------|--------------|------|
|
||
| AC-1.3 60 s 硬上限 | `startup-pipeline.md` L156 + `TDD-v2.md` 索引 L18 | ✅ |
|
||
| AC-1.3a 6 階段進度 | `startup-pipeline.md` §3 + §6 前端 render | ✅ |
|
||
| AC-1.3b 20 s 重試提示 | `startup-pipeline.md` L332-346 | ✅ |
|
||
| AC-1.3c 60 s Error state + 三按鈕 | `startup-pipeline.md` L325-330 + `startup-panel.js` showStartupError | ✅ |
|
||
| AC-1.3d 階段文字 Design | `startup-pipeline.md` L517 | ✅ |
|
||
| AC-7.7 OS 通知並存 | `server-lifecycle.md` §10 三平台 + L791「兩者互補,不取代」 | ✅ |
|
||
| AC-2.1 日常啟動 ≤5 s | `server-lifecycle.md` §2.1a 日常啟動 ~3.8 s | ✅ |
|
||
| AC-2.1a 每次 Start 都自動開 | `control-panel.md` L491-503 | ✅ |
|
||
|
||
---
|
||
|
||
## E. Architect 自補項檢查
|
||
|
||
- **F-2 Restart port 強制保留**:`server-lifecycle.md` §3.2(L179-224)完整,含 `pickPort(preferredPort, forceMatch=true)` + 使用者情境解釋 + port 被佔則進 Error state + 廣播 shutdown-imminent ✅
|
||
- **階段 6 WebSocket 實作細節**:`startup-pipeline.md` L120-130 列出 long-poll endpoint / sentinel file 兩方案,L515 明示「M8-4b 執行者決定並回報」。PM 接受此處延後決策,因為兩方案介面簽名相容,不影響上游 M8-4 開發 ✅
|
||
- **boot-id crypto/rand 16 bytes**(Q3):索引 L29 v2.1 變更條目記錄,不引額外依賴 ✅
|
||
- **navigator.language fallback 強化**(Q5):索引 L30 記錄 ✅
|
||
|
||
---
|
||
|
||
## F. 工時合理性
|
||
|
||
v2.0 10 人天 → v2.1 12 人天(+2 天),增量分解:
|
||
- R5-E 階段化啟動 +1 天(M8-4b 新 milestone)
|
||
- R5-D1 OS 通知 +0.3 天
|
||
- PM Q4 7+1 shutdown modal +0.2 天
|
||
- Minor 4 WebSocket 廣播 +0.3 天
|
||
- 其餘(M8-10 驗收項目擴充)+0.2 天
|
||
|
||
合計 2.0 天,與 milestone-plan.md L491 總計相符。
|
||
|
||
**PM 接受 12 人天**;另 L493 建議「加 M8-3 / M8-4 buffer 1 天 → 對使用者回報 ~13 人天」也合理。
|
||
|
||
**輕微瑕疵**:`milestone-plan.md` L6 文字摘要寫「~11.5 人天」,L491 合計表寫「12.0」。摘要行未同步更新,屬文字不一致。非阻擋。
|
||
|
||
---
|
||
|
||
## G. 第二輪新發現問題
|
||
|
||
### Major
|
||
|
||
無。
|
||
|
||
### Minor
|
||
|
||
#### Minor 6(新):`code-reuse-v2.md:92` 殘留 v2.0 字串
|
||
|
||
**問題**:`v2/code-reuse-v2.md` L92 仍然寫
|
||
|
||
```
|
||
第 83 行:砍 `mockMode` 欄位;新增 `ctrl` / `logBuf` / `prefs` / `autoOpenedThisSession` 欄位
|
||
```
|
||
|
||
**但** `milestone-plan.md:147` / `server-lifecycle.md:923` / `control-panel.md:811` 全部明示「**不存在** `autoOpenedThisSession`」。這是 v2.0 → v2.1 轉版時 `code-reuse-v2.md` 沒同步更新。
|
||
|
||
**影響**:M8-4 / M8-5 的工程師 Agent 若以此為唯一指引,可能加回 `autoOpenedThisSession` 欄位 → 違反 R5-D3。
|
||
|
||
**修正**:`code-reuse-v2.md:92` 移除 `autoOpenedThisSession` 字樣。一行 diff。
|
||
|
||
**阻擋等級**:Minor。不阻擋進開發(因其他三處指引都正確),但進 M8-5 前最好修掉避免混淆。
|
||
|
||
#### Minor 7(新):`milestone-plan.md:6` 摘要 11.5 vs 合計 12.0
|
||
|
||
見 §F。非阻擋,下次順手改。
|
||
|
||
---
|
||
|
||
## H. 第二輪通過 / 不通過 結論
|
||
|
||
### 結論:**通過**(附 Minor 6 / Minor 7 可隨開發過程修)
|
||
|
||
| 項目 | 第一輪 | 第二輪 |
|
||
|------|-------|-------|
|
||
| Major | 4 | **0** |
|
||
| Minor | 5 | **2**(新 Minor 6 / 7,非阻擋)|
|
||
| R5-E 落地 | — | **100%** |
|
||
| 是否阻擋進 M8 開發 | 部分阻擋 | **不阻擋** |
|
||
| 整體品質 | 8.5/10 | **9.3/10** |
|
||
|
||
### 稱讚
|
||
|
||
- R5-D1 OS 通知的三個觸發點(Start 失敗 / watchServer 3 次失敗 / startup pipeline 失敗)全覆蓋 + 非阻塞 goroutine 設計完美
|
||
- `startup-pipeline.md` 是一份**程式碼級**的子檔(518 行 Go + JS 全部寫出),Reviewer 可以直接比對
|
||
- R5-E5 階段文字零硬編碼,完全以 i18n key 交給 Design,等 Design Spec v2.1 敲定就能直接填 JSON
|
||
- §11.6 的 450 MB 範圍澄清 + 悲觀情境 fallback 策略很到位
|
||
- `deletions.md` Minor 5 修正後的驗收 grep 命令可直接複製執行,防呆
|
||
|
||
### 下一步
|
||
|
||
1. 使用者確認 TDD v2.1 + Design Spec v2.1 → 即可進 M8 開發
|
||
2. **M8-1 / M8-2 / M8-3 / M8-8 可立即並行啟動**(互不依賴;Major 修復未觸及此四者)
|
||
3. **M8-4 / M8-4b / M8-5 / M8-9** 依賴 `DefaultPreferences()` / `sendCrashNotification()` / `StartupPipeline` / Preferences 持久化,這四者 TDD v2.1 都已 spec 完整可開工
|
||
4. Minor 6 / Minor 7 建議 M8-5 啟動前或開發過程中順手修
|
||
5. 本輪 PM 審閱完成,不需第三輪
|
||
|
||
- **審閱耗時**:~1 小時(含所有子檔閱讀 + 交叉對照)
|