依 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>
37 KiB
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 |
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 功能。TDDffmpeg-lgpl.md§11 已警示這點,但deletions.md§1.1 的敘述「可從 RTSP stream 呼叫」不精準,實際是「從本地影片 seek 呼叫」
E.3 誤解 R5 / R5-D
- R5-4 理解錯誤(Major #1):TDD 把「首次啟動自動開瀏覽器」實作成 per-session-once(
autoOpenedThisSessionflag),但 R5-D3 明確修正語意為「每次 Start Server 成功都開」。這個誤解在milestone-plan.mdM8-9 和control-panel.md§4.6 各出現一次,是系統性誤解 - R5-D1 / R5-D2 整組遺漏(Major #1 包含)
- R5-2 理解正確:TDD 明確寫
OnBeforeClosereturn false、不 hide-to-tray、有shutdownGracePeriod5s → 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 要求三處文案到位:
- Wails Close 按鈕 tooltip / confirm dialog(AC-7.6)— TDD 未落地(main.go 只寫了
OnBeforeClose: return false,沒加 confirm 對話框邏輯;是否加是 Design Agent 的 scope,但 TDD 應該預留 hook) - Offline Overlay 文案引導 —
web-ui-offline-overlay.md§4.6 ✅ - 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:errorevent,沒叫osascript display notification/powershell New-BurntToastNotification/notify-send - R5-D2(Linux 預設自動開瀏覽器 OFF):
preferences.go沒有依runtime.GOOS分平台 default 的邏輯 - R5-D3(每次 Start 都開,不只首次):
autoOpenedThisSessionflag 和 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,例如:
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 五態的思考完整(
txMuvsmu雙 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)被當成技術問題丟回使用者
下一步建議
- Orchestrator 啟動 Architect Agent,做 TDD v2 patch,聚焦修 Major #1 ~ #4
- 修完後 Orchestrator 再啟動 PM 做第二輪審閱(只審 patch 部分,~30 分鐘)
- 同時 Orchestrator 啟動 Design Agent 做 Design 交叉審閱(本輪 PM 審閱發現 AC-7.6 是 Design 的未決項)
- 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.mdv2.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.mdL120-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.mdMinor 5 修正後的驗收 grep 命令可直接複製執行,防呆
下一步
- 使用者確認 TDD v2.1 + Design Spec v2.1 → 即可進 M8 開發
- M8-1 / M8-2 / M8-3 / M8-8 可立即並行啟動(互不依賴;Major 修復未觸及此四者)
- M8-4 / M8-4b / M8-5 / M8-9 依賴
DefaultPreferences()/sendCrashNotification()/StartupPipeline/ Preferences 持久化,這四者 TDD v2.1 都已 spec 完整可開工 - Minor 6 / Minor 7 建議 M8-5 啟動前或開發過程中順手修
- 本輪 PM 審閱完成,不需第三輪
- 審閱耗時:~1 小時(含所有子檔閱讀 + 交叉對照)