jim800121chen 8cd5751ce3 feat(local-tool): M8 重構 — Wails 控制台 + 瀏覽器 Web UI(R5 決策)
依 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>
2026-04-15 17:57:54 +08:00

37 KiB
Raw Blame History

PM 交叉審閱 TDD v22026-04-14

審閱者PM Agent 被審物:/Users/jimchen/visionA/local-tool/.autoflow/04-architecture/TDD-v2.md + v2/ 全 8 份子檔 對照基準:PRD-v2.md484 行)+ R5 五輪決策 + R5-D 補充決策 審閱範圍需求面完整度、R5 / R5-D 落地、PM 5 懸念的技術回答、體驗紅線


摘要

  • 總結論:需小改後通過(不是大改、不阻擋整體方向)
  • 問題數Major 4 個 / Minor 5 個
  • 是否阻擋進開發部分阻擋——Major #1 / #2R5-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.mdN1 / N2+ web-ui-offline-overlay.mdN3+ server-lifecycle.md §9N4+ cors-security.mdN5+ control-panel.md §4.7N6 六項皆有專屬子檔
§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 sizeidle 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-6N-R2 吸收進 R-v2-2N-R3 部分吸收進 AC-7.6 要求N-R4 無明確對應 ⚠️ 見 Minor #1N-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-5aMock 完全砍) deletions.md §2 + §3 + §6、milestone-plan.md M8-2 涵蓋 Go / Wails app / 前端 / i18n / CLI flag 所有層
R5-6LGPL 方案 B ffmpeg-lgpl.md §1 表格
R5-6amacOS decoder-only ~20 MB ffmpeg-lgpl.md §2.3 configure flags + §2.2 feature set 驗收條件 < 25 MB 合理
R5-6bmacOS 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-7M7 Windows 先不管) milestone-plan.md M8-10 註解「R5-7 同意先不管,這次順帶驗證」 沒偷跑 M7 Windows但又不漏驗
共識 #14boot-id server-lifecycle.md §9 + web-ui-offline-overlay.md 端到端

B.2 R5-D 補充決策3 條)— 這是最大問題

決策 應落地位置 落地狀況
R5-D1Server 崩潰時保留 OS 原生通知 control-panel.md §4.7 watchServer Error state / server-lifecycle.md §6 watchServer 或 control-panel.md §4 ServerController.setState 完全缺失。`grep -ri 'osascript
R5-D2Linux 預設 openBrowserOnStart=OFFmac/Win = ON preferences.go(預設值載入邏輯)或 control-panel.md §4.3 Preferences struct 預設 完全缺失。TDD 只定義 Preferences.OpenBrowserOnStart bool,沒有 NewDefaultPreferences()runtime.GOOS 分平台初值的邏輯。若 preferences.json 不存在,使用者第一次啟動在 Linux 上會用 Go zero valuefalse剛好誤中但這是 碰巧對,不是設計對
R5-D3每次 Start 成功都自動開瀏覽器(不只首次) control-panel.md §4.6 ServerController.Start 末段 實作與決策相反。現況是 if c.app.prefs.OpenBrowserOnStart && !c.app.autoOpenedThisSessionautoOpenedThisSession 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 localStoragev1 沿用)。兩邊分離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/3AC-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 #1TDD 把「首次啟動自動開瀏覽器」實作成 per-session-onceautoOpenedThisSession 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 sv1 經驗)
  • Go server spawn + waitHealthy1-3 s依 Python sidecar 啟動)
  • OpenInBrowser(""):系統 call ~0.1 s
  • 瀏覽器冷啟動(第一次點開 Chrome/Safari/Edge3-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 conditionWails 關閉視窗 → StopServer 過程中瀏覽器 tab

TDD v2 R-v2-4 已明確提到這個 race condition0.5-5s 內瀏覽器可能看到 ECONNREFUSED 但 Overlay 還沒觸發),並分析為「實務上使用者關 Wails 通常也會關瀏覽器 tabrace 不構成實際問題若真的沒關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 dialogAC-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 #3TDD 應在 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 #1R5-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-D2Linux 預設自動開瀏覽器 OFFpreferences.go 沒有依 runtime.GOOS 分平台 default 的邏輯
  • R5-D3每次 Start 都開,不只首次)autoOpenedThisSession flag 和 milestone-plan M8-9 驗收條件明確是 per-session-once和決策相反

應加在

決策 子檔 修正位置
R5-D1 control-panel.md §4.7watchServer 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() Preferencesruntime.GOOS == "linux"OpenBrowserOnStart: false,否則 truepreferences.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 #2R5-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 #3PM 5 懸念中 AC-1.310 秒)與 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 MBWebView2 / WKWebView 基礎)
    Go server 子程序    ~40-60 MBGin + 8 個預載入 .nef metadata
    Python sidecar      ~150-220 MBPython 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 #4shutdownGracePeriod 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 #1N-R4CI / 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 #2AC-2.1(日常啟動 ≤ 5 秒)沒估算

和 Major #3 同源。若 Major #3 補上冷啟動預算分解,順便也覆蓋 AC-2.1


Minor #3AC-7.6Wails 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 #4Wails 關閉時可主動通知瀏覽器 tab 避免 15 s race condition

見 §F.2。建議在 Wails OnBeforeClose 之前透過 WebSocket 廣播 server:shutdown-imminent 訊息,前端立即顯示 Overlay。成本低server 端 ~3 行、前端 ~10 行),體驗提升明顯。

阻擋等級Minor可在 M8-10 smoke test 發現體驗不佳時補


Minor #5deletions.md §1.1 「RTSP stream 可能呼叫 NewVideoSourceFromURL」敘述不精準

實際 grep 顯示 camera_handler.go:731NewVideoSourceFromURLWithSeek 處理本地上傳影片的 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 AgentTDD 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.md484 行)
  • TDD v2 索引/Users/jimchen/visionA/local-tool/.autoflow/04-architecture/TDD-v2.md136 行)
  • 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.12026-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 新增 §10L690-791notify.go + macOS osascript / Linux notify-send -u critical / Windows PowerShell BurntToast + msg * fallbackcontrol-panel.md L482-486 在 Start 失敗時 go sendCrashNotification(...)§6 diffL405-408在 watchServer 3 次失敗時 fire-and-forgetstartup-pipeline.md L295-299 pipeline 失敗時一併發通知 完整。觸發點三處Start 失敗 / watchServer 3 次失敗 / startup pipeline 失敗)都有 non-blocking go 呼叫
Major 1 R5-D2 Linux 預設 OFF server-lifecycle.md §11.3L824-850DefaultPreferences()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「多次按 RestartAutoOpenBrowser=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.1aL83-90補上日常啟動 ~3.8 s 估算 以 R5-E 取代是合理方案
Major 3 §11-3 450 MB 範圍 server-lifecycle.md §11.6L925-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)§8L499-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.1aL83-90日常啟動 ~3.8 s 估算表 + 結論「遠低於 60 s 上限」
Minor 3 OnBeforeClose confirm hook server-lifecycle.md §7L475-479寫死 return false + 註解「不跳確認對話框」,未預留 ConfirmOnClose hook;但 Architect 在 L490-493 解釋「Wails v2 default 就是直接關,與 R5-2 語意一致,不加 hook」 ⚠️ 以等效方案處理。PM 第一輪建議是「預留 hook」方便未來改動Architect 選擇「保持乾淨不加 dead code」。兩者語意均不彈對話框符合 R5-2PM 接受此取捨,非阻擋
Minor 4 WebSocket shutdown-imminent server-lifecycle.md §2.3 t=0.005 / §8 L511 / web-ui-offline-overlay.md §3.2aL78-95+ L158 新增 use-shutdown-watcher.ts + L361 訂閱處理server 新增 /ws/system endpointWails 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:731handleVideoSeekvideoIsURL 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 > startupHardTimeoutFail()
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 §2L87-105只定義 i18n keystartup.stage.{1-6}.label),實際文字留給 i18n/zh-TW.json / en-US.jsonDesign 填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.2L179-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 bytesQ3索引 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 6code-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 7milestone-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 小時(含所有子檔閱讀 + 交叉對照)