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

28 KiB
Raw Permalink Blame History

v2/milestone-plan.md — M8 開發 milestone 拆分

所屬TDD v2 §2.7 版本v2.12026-04-14 吸收 PM 審閱 + R5-D + R5-E 目的:給 Orchestrator 調度工程師 Agent 用。每個 milestone = 一個 Reviewer 審查單位。 總工時估算:~12.0 人天v2.0 是 10 人天R5-E 階段化啟動 +1 天 / PM Q4 7+1 秒 shutdown modal +0.2 天 / R5-D1 OS 通知 +0.3 天 / Minor 4 WebSocket 廣播 +0.3 天 / M8-10 驗收項目擴充 +0.2 天,合計 +2 人天。與 §3 合計 12.0 一致)


1. M8 全景

v2.1 的開發總共拆成 11 個 milestone編號 M8-1 ~ M8-10 + M8-4b。依賴關係

M8-1 (砍 yt-dlp) ──────┐
                       ├──▶ M8-4 (server lifecycle) ──▶ M8-4b (階段化啟動) ──▶ M8-5 (Wails UI 改寫) ──▶ M8-9 (boot-id + 重連)
M8-2 (砍 Mock) ────────┘                                                                                    ▲
                                                                                                            │
M8-3 (ffmpeg LGPL vendor) ─────────────────────────────────────────────────────────────────────────────────┼──▶ M8-10 (end-to-end)
                                                                                                            │
M8-6 (source-selector 調整) ────────────────────────────────────────────────────────────────────────────────┤
                                                                                                            │
M8-7 (Offline Overlay) ─────────────────────────────────────────────────────────────────────────────────────┤
                                                                                                            │
M8-8 (CORS) ────────────────────────────────────────────────────────────────────────────────────────────────┘
  • M8-1 / M8-2 / M8-3 / M8-8 可以同時開工(互不依賴)
  • M8-4 依賴 M8-1因為 app.go 內的 mockMode field 會在 M8-2 砍掉;但 M8-4 主要碰 server_control.go / log_buffer.go 不衝突,實務可平行)
  • M8-4b 依賴 M8-4(需要 ServerController 已存在才能插入 pipeline hook
  • M8-5 必須等 M8-4b 完成bindings + event schema 定義完)
  • M8-6 可以獨立進行
  • M8-7 依賴 M8-4需要 boot-id server 端 + WebSocket hub會在 M8-4 一併做)
  • M8-9 依賴 M8-4 + M8-4b + M8-7
  • M8-10 必須全部完成後

2. Milestone 明細

M8-1砍 yt-dlp 全套

預估0.5 人天

負責 AgentBackend Agent + Frontend Agent同時進行因為跨 Go + TS

依賴:無

任務

  1. v2/deletions.md §1 刪後端 Govideo_source.go / camera_handler.go / router.go / deps/checker.go / main.go 註解)
  2. v2/deletions.md §5 刪前端 TSsource-selector.tsx 的 URL tab / camera-store.tsstartFromUrl / i18n 4 個 key
  3. v2/deletions.md §4.1 + §4.2 + §4.3 + §4.4 + §4.5 刪打包流程Makefile / installer / bootstrap
  4. 刪除 /vendor/yt-dlp/ 目錄(若存在)

驗收條件

  • go build ./... PASS
  • go test ./server/... PASS
  • pnpm --dir frontend build PASS
  • git grep -i 'yt-dlp\|ytdlp\|ResolveWithYTDLP\|StartFromURL\|classifyVideoURL\|pasteUrl\|urlPlaceholder' 無業務程式碼 match.autoflow/ 內的歷史文件可接受)
  • make payload-macos 可成功(不再 cp yt-dlp

Reviewer 檢查重點

  • camera_handler.go 砍掉後 import 清理乾淨
  • i18n types.ts / zh-TW.ts / en.ts 三檔同步刪
  • Makefile 三個平台的 vendor-ytdlp* target 都清

M8-2砍 Mock 模式全套

預估0.5 人天

負責 AgentBackend Agent + Frontend Agent

依賴:無(理論上可與 M8-1 平行)

任務

  1. v2/deletions.md §2 + §3 刪 Go兩個檔整檔刪、device/manager.go / camera/manager.go / config.go / main.go / app.go 所有 mockMode 條件砍掉)
  2. v2/deletions.md §6 刪前端 Mock 切換 UI + 4 個 i18n key
  3. 更新 noDevices 文字empty state 友善化)

驗收條件

  • Go + 前端 build PASS
  • git grep -i 'VISIONA_MOCK\|MockMode\|mockMode\|MockCamera\|MockDriver\|NewMockDriver\|runtimeModeMock' 無業務程式碼 match
  • 實機啟動 server沒插 Kneron 硬體)→ Devices 頁顯示友善 empty state「未偵測到 Kneron 裝置」

Reviewer 檢查重點

  • NewManager 簽名改動後所有呼叫點都更新
  • --mock / --mock-camera / --mock-devices CLI flag 真的拿掉server --help 不再列)
  • 任何剩下的 test 檔案不再依賴 mockMode

M8-3ffmpeg LGPL vendor 切換

預估1.5 人天

負責 AgentDevOps Agent+ Backend Agent支援 macOS build 環境)

依賴:無

任務

  1. Windows / Linux0.5 人天):依 v2/ffmpeg-lgpl.md §3 + §4 修改 Makefile 的 URL 與 vendor target實測 make vendor-ffmpeg-windows / vendor-ffmpeg-linux 可成功
  2. macOS build script1 人天):
    • v2/ffmpeg-lgpl.md §2.3 新增 vendor-ffmpeg-macos-build Makefile target
    • 第一次跑:下載 ffmpeg n7.1 source、算 sha256 填進 Makefile、跑 configure + make、驗證 binary size < 25 MB、驗證 ffmpeg -version 不含 --enable-gpl / libx264 / libx265
    • 把 ffmpeg + ffprobe + COPYING.LGPLv3 + BUILD.md 進 git commit 到 vendor/ffmpeg/macos/
    • 修改 .gitignorevendor/ffmpeg/macos/ 成為 git tracked
    • 修改 vendor-ffmpeg target 改為「驗證 binary 存在且是 LGPL」
  3. Payload 階段同步§7三個平台的 payload-* target 改為 copy ffmpeg + ffprobe + COPYING.LGPLv3,不再 copy yt-dlpM8-1 會處理刪)
  4. Installer 同步§8
    • installer/windows/visiona-local.iss 新增 ffprobe 和 LGPL license 行
    • installer/linux/build-appimage.sh 迭代 tool 改為 ffmpeg ffprobe

驗收條件

  • make vendor-ffmpeg PASSmacOSbinary size OKv2/ffmpeg-lgpl.md §10 驗收表)
  • vendor/ffmpeg/macos/ffmpeg -version | grep -- --enable-gpl 無輸出
  • spctl --assess --verbose vendor/ffmpeg/macos/ffmpeg → accepted
  • make vendor-ffmpeg-windows + vendor-ffmpeg-linux 可成功,檔案解出後 version 無 --enable-gpl
  • make payload-macos 成功,payload/darwin/bin/ 內有 ffmpeg / ffprobe / ffmpeg-COPYING.LGPLv3
  • 實測用 vendor/ffmpeg/macos/ffmpeg 解 5 種格式mp4/avi/mov/mpeg/mpg正常

Reviewer 檢查重點

  • vendor/ffmpeg/macos/BUILD.md 內容正確、可重現
  • 開發者用單一 make vendor-ffmpeg-macos-build 指令真的能從零 build 出來(不依賴本機 cached state
  • .gitignore! rule 順序正確
  • Makefile 的 configure flags 與 v2/ffmpeg-lgpl.md §2.3 完全一致

M8-4Server lifecycle + log buffer + bindings + OS notify + Preferences

預估2 人天v2.0 是 1.5 天;+0.3 天 R5-D1 OS 通知、+0.2 天 PM Q4 7+1 秒 shutdown modal

負責 AgentBackend AgentGo / Wails

依賴M8-1避免 mockMode 欄位被砍後衝突)。實務上可與 M8-2 平行。

任務

  1. 新增檔案v2/control-panel.md §7 + §4
    • visiona-local/log_buffer.go~120 行 ring buffer
    • visiona-local/server_control.go~280 行 ServerController + state machine + startServerV2 + logPump + 7+1 秒 shutdown modal timer,見 server-lifecycle.md §8.2
    • visiona-local/preferences.go~80 行)— R5-D2:必須實作 DefaultPreferences()runtime.GOOS 分平台預設macOS/Windows AutoOpenBrowser=true、Linux false+ atomic write-renameserver-lifecycle.md §11.3
    • visiona-local/notify.go~100 行)— R5-D1:三平台 OS 通知macOS osascript display notification / Linux notify-send / Windows PowerShell BurntToast + msg * fallback詳見 server-lifecycle.md §10
  2. 修改 visiona-local/app.go
    • 新增 ctrl / logBuf / prefs 欄位(不要 autoOpenedThisSession — R5-D3 已砍掉 per-session-once 概念)
    • GetBootstrapStatus / setBootstrapStatus / bootstrapStatus 欄位
    • watchServer() 失敗後行為(不 os.Exit改設 Error state + goroutine 呼叫 sendCrashNotification — R5-D1
    • 新增 12 個 Wails bindingsv2/control-panel.md §4.2
  3. 修改 visiona-local/main.go
    • OnBeforeClose handler
    • shutdownGracePeriod 5 s → 7 sPM Q4 定案,不是 10 s
  4. 修改 server/main.go + system_handler.go + router.go
    • 新增 boot-id 生成(使用 crypto/rand 16 bytes → hexArchitect Q3 決定,不引 google/uuid
    • 新增 GET /api/system/boot-id endpoint
    • Minor 4server 新增 WebSocket hub 廣播 server:shutdown-imminent 的能力(新增 /ws/system endpoint 或擴充現有 /ws/server-logsWails 的 ctrl.Stop() 在開始 SIGTERM 前透過 HTTP 或 server 內部 API 觸發此廣播
    • 對齊 shutdown timeoutserver/main.go:166shutdownFn timeout 10 s → 6 sWails 7 s 減 1 s確保 server 先完成 cleanup

驗收條件

  • cd visiona-local && go build . PASS
  • cd server && go build ./... PASS
  • 12 個 bindings 在 visiona-local/frontend/wailsjs/go/main/App.d.ts 正確生成
  • 手動測試:新 bindings 可從 Wails dev 模式的 devtools console 呼叫
  • 手動測試Start → server 啟動 → 看 log 噴進 LogBuffer → 用 GetRecentLogs(20) 取回
  • 手動測試Stop → 1 秒內顯示「停止中…」modal若 server 未秒 exit+ 7 秒內 SIGKILL若卡死
  • 手動測試Restart → state: Running → Stopping → Stopped → Starting → Running
  • 手動測試:殺 server process → watchServer 3 次失敗後 state → Error + 收到 OS 原生通知(三平台都要)
  • R5-D2 驗收:全新使用者第一次在 macOS/Windows 開 app → preferences.json 不存在 → DefaultPreferences() 回傳 AutoOpenBrowser=trueLinux 上回傳 false
  • curl http://127.0.0.1:<port>/api/system/boot-id 回傳 JSON 含 bootIdhex 32 字元)
  • WebSocket server:shutdown-imminent 廣播:用 websocat 連 /ws/system,按 Stop 後秒收到廣播

Reviewer 檢查重點

  • LogBuffer thread-safetymutex 正確)
  • ServerController 的 txMu / mu 互斥
  • logPump 在 server 死掉 / pipe EOF / 高頻 stdout 下都不 leak goroutine
  • parseLogLevel 不會 panic on 空字串或非 ASCII
  • boot-id 生成使用 crypto/rand + encoding/hex(不引 google/uuid
  • Preferences JSON atomic writetmp + rename見 §11.3
  • DefaultPreferences()runtime.GOOS 正確切換Linux 分支有測試)
  • sendCrashNotification 三平台檔案darwin/linux/windows build tag齊全且不阻塞
  • shutdown modal 的 1 秒 timer 與 7 秒 grace timer 正確goroutine 泄漏檢查)
  • server 端 WebSocket hub 廣播邏輯與 Wails 端呼叫的契約(傳遞 reason 欄位)

M8-4b階段化啟動管線R5-E

預估1 人天v2.0 未拆出v2.1 新增)

負責 AgentBackend AgentGo / Wails+ Frontend Agentvanilla JS startup panel

依賴M8-4需要 ServerController + bindings

任務:依 v2/startup-pipeline.md(若該檔不存在則落在 control-panel.md §3.1 + server-lifecycle.md §2.1

  1. 新增 visiona-local/startup_pipeline.go~180 行):
    • StartupPipeline struct含 currentStage / stageStartedAt / startedAt / softTimeout / hardTimeout
    • NewStartupPipeline(ctx) / Start(stage int) / Complete(stage int) / Fail(stage int, err) / Ready() 方法
    • watcher(ctx) goroutine每秒檢查 soft timeout (20 s) + hard timeout (60 s)emit startup:stage-timeout / startup:error event
    • 4 個 event schemastartup:progress / startup:stage-timeout / startup:error / startup:ready(見 v2/startup-pipeline.md §1
  2. 修改 visiona-local/app.gostartup(ctx)
    • OnStartup 最前面初始化 pipeline 並 Start(1)
    • 每個既有階段的對應處呼叫 pipeline.Complete(N) + pipeline.Start(N+1)
    • 6 個階段對應1=Wails init、2=Python runtime、3=server binary spawn + health check、4=first /api/devices 查詢、5=OpenInBrowser call、6=WebSocket 首個 client connect
    • 最後 pipeline.Ready() → emit startup:ready
  3. 修改 server 端
    • pkg/ws(或對應的 WebSocket hub新增 OnClientConnected callback讓 Wails 能收到「第一個 client 連上」的通知(透過 WebSocket 或 HTTP poll
    • Wails 的 startup_pipeline.go 訂閱此通知完成階段 6
  4. 失敗處理:任一階段失敗 → pipeline.Fail(stage, err)ctrl.setState(Error, ...) + sendCrashNotification + emit startup:error
  5. 非阻塞:所有 EventsEmit 呼叫都走 buffered channel 或 fire-and-forget goroutine不阻塞啟動流程
  6. 前端Wails 控制台)
    • visiona-local/frontend/components/startup-panel.js~100 行)
    • 訂閱 4 個 event 更新 DOM
    • 收到 startup:ready 淡出300 ms ease
    • i18n key 從 i18n/zh-TW.json / en-US.json 讀(文案由 Design Spec v2.1 敲定)

驗收條件

  • cd visiona-local && go build . PASS
  • 正常冷啟動樂觀情境4 s 內看到「啟動進度面板」6 階段逐一 completed最後淡出顯示主控台
  • 慢啟動情境mock 階段 2 jam 25 秒 → 看到「階段 2 正在重試 …」副文字
  • 失敗情境mock 階段 3 fail → 看到進度面板變紅、切 Error state、收到 OS 通知、發 startup:error event
  • 總時 > 60 秒情境mock 每個階段都等 12 秒 → 60 秒後進 Error statewatcher 總時檢查有效)
  • 日常啟動(非首次):~3 s 內就緒,符合 PM AC-2.1

Reviewer 檢查重點

  • watcher goroutine 在 pipeline.Ready() / Fail() 後正確停止,不 leak
  • EventsEmit 非阻塞(若 Wails IPC 慢也不拖啟動)
  • 6 階段的 labelKey 與 design-spec v2.1 一致
  • 失敗後 Error state 透過 ctrl.setState 觸發,不繞過 state machine
  • startup-panel.js 的淡出動畫不會卡住主控台 UI

M8-5Wails 控制台 vanilla UI 改寫

預估2 人天

負責 AgentFrontend Agentvanilla JS

依賴M8-4需要 bindings + events

任務

  1. 改寫 visiona-local/frontend/index.html 成控制台 layoutv2/control-panel.md §3
  2. 改寫 visiona-local/frontend/app.js 成控制台主程式§5
  3. 改寫 visiona-local/frontend/style.css 新增 status card / log panel / action bar / preferences 樣式,並加 light/dark mode CSS variables
  4. 新增 components
    • components/status-card.js
    • components/log-panel.js(含 virtual scroll lite + batch render
    • components/action-bar.js(含 disable 矩陣)
    • components/preferences.js
  5. 新增 i18n
    • i18n/en-US.json / zh-TW.json
    • i18n/loader.js
  6. 新增 iconsicons/*.svg × 6

驗收條件

  • Wails dev 模式下開 app
    • 看到控制台 UIstatus / log / actions / preferences不是 splash / blank / Next.js
    • Start 按鈕能啟動 server狀態卡片變 Running 綠色 badge
    • Log panel 即時顯示 server stdout看到 Gin log
    • Stop 按鈕能停 serverbadge 變灰
    • Restart 按鈕能重啟(狀態過程正確)
    • Open in Browser 開瀏覽器到正確 URL
    • Reveal Logs 開啟 <dataDir>/logs/ 資料夾
    • Clear Logs 清畫面但不動磁碟檔
    • Preferences 切 openBrowserOnStart 持久化到 preferences.json
    • Dark mode 跟隨系統macOS 切換 Dark Mode 後控制台自動變色)
    • Log panel 按 Pause 後自動捲動停止,解除後恢復
  • make wails-macos 能 build 出 .app打開後看到控制台 UIM7-B 教訓)

Reviewer 檢查重點

  • vanilla JS 沒引入任何 npm 依賴(package.json 不存在於 visiona-local/frontend/
  • Log panel 在高頻1000 行/秒)下不會 freeze UIbatch render 有效)
  • i18n loader SSR-safe不適用但 Wails 沒 SSR 概念)
  • icons/ 下的 SVG 符合 inline 使用的簡潔規則viewBox + single path
  • Action bar 在每個 state 下 button enable/disable 正確disable 矩陣全部驗證)

M8-6Web UI source-selector + 副檔名擴充

預估0.5 人天

負責 AgentFrontend Agent

依賴:無(與其他 milestone 平行)

任務

  1. frontend/src/components/camera/source-selector.tsxv2/deletions.md §5.1 移除 URL tab + mode toggle
  2. accept=".mp4,.avi,.mov" 改為 accept=".mp4,.avi,.mov,.mpeg,.mpg"
  3. i18n 新增 videoFormats key 或改既有 mp4AviMov
  4. 後端同步server/internal/api/handlers/camera_handler.go:251 把 extension whitelist 從 .mp4 / .avi / .mov 擴充為 5 個:
    -if ext != ".mp4" && ext != ".avi" && ext != ".mov" {
    -    c.JSON(400, gin.H{"success": false, "error": gin.H{"code": "BAD_REQUEST", "message": "only MP4/AVI/MOV files are supported"}})
    +if ext != ".mp4" && ext != ".avi" && ext != ".mov" && ext != ".mpeg" && ext != ".mpg" {
    +    c.JSON(400, gin.H{"success": false, "error": gin.H{"code": "BAD_REQUEST", "message": "only MP4/AVI/MOV/MPEG/MPG files are supported"}})
     }
    

驗收條件

  • 前端 build PASS
  • 手動測試5 種副檔名都能上傳且正常開始推論
  • UI 不再有「Paste URL」按鈕

Reviewer 檢查重點

  • handler 裡的 extension 比對是 strings.ToLower 後再比,大小寫不敏感
  • 錯誤訊息中英文同步

M8-7Web UI Server Offline Overlay

預估1 人天

負責 AgentFrontend Agent

依賴M8-4需要 server 端 boot-id endpoint

任務:依 v2/web-ui-offline-overlay.md §4 的 7 個檔案清單:

  1. 新增 frontend/src/stores/system-store.ts
  2. 新增 frontend/src/hooks/use-boot-id-watcher.ts
  3. 新增 frontend/src/components/server-offline-overlay.tsx
  4. 新增 frontend/src/components/boot-id-watcher-mount.tsx
  5. 修改 frontend/src/app/layout.tsx(掛 overlay + watcher
  6. 修改 frontend/src/lib/i18n/{types,zh-TW,en}.ts(新增 serverOffline 區塊)

驗收條件

  • pnpm --dir frontend build PASS含 SSR 相容性驗證)
  • 手動測試:
    • 正常啟動沒 overlay
    • 關 Wails 視窗後 15 s 內瀏覽器 tab 顯示 overlay
    • 重新開 Wails app → Web UI 的「重試」按鈕有效overlay 消失)
    • 控制台按 Restart → boot-id 變 → 自動 window.location.reload()
    • 切到別的 tab 5 分鐘再切回 → polling 立即 probe 不用等

Reviewer 檢查重點

  • Zustand store 的 failures state 遞增正確
  • use-boot-id-watcher.ts 的 cleanupuseEffect return正確 cancel
  • AbortSignal.timeout(3000) 在舊瀏覽器相容性(若需支援 Chrome < 103 則 fallback
  • Overlay 元件 a11yrole="alertdialog" + aria-labelledby
  • i18n 新增 key 三個檔同步types / zh-TW / en

M8-8CORS + origin check middleware

預估0.5 人天

負責 AgentBackend Agent

依賴:無

任務:依 v2/cors-security.md §4 + §5

  1. 覆寫 server/internal/api/middleware.go(白名單版)
  2. 新增 server/internal/api/ws/origin.go
  3. 修改所有 WS handler 的 upgrader CheckOrigin
  4. 新增 server/internal/api/middleware_test.go 單元測試(TestIsAllowedOrigin
  5. router.gorequireSameOriginOrNoOrigin middleware 到 /api/*

驗收條件

  • go test ./server/internal/api/... -run TestIsAllowedOrigin PASS
  • v2/cors-security.md §9 所有 curl 驗收條件通過
  • WS websocat 測試:白名單 origin 可連、非白名單擋
  • 既有 Next.js Web UI 的 fetch 仍正常運作same-origin + white list

Reviewer 檢查重點

  • isAllowedOrigin 對 edge case 處理正確(空字串 / null / https / 不合規 URL
  • 不 accidentally 把 X-Relay-Token header 保留relay 早已砍)
  • WS upgrade 在非白名單 origin 下回 403不是靜默失敗
  • Middleware 套用順序middleware.go 的 CORSMiddleware 要在 requireSameOriginOrNoOrigin 之前)

M8-9Boot-ID 端到端整合 + Restart 重連

預估1 人天

負責 AgentBackend Agent + Frontend Agent

依賴M8-4 + M8-7

任務

  1. 整合驗證M8-4 的 server 端 boot-id API + M8-7 的前端 polling 串起來
  2. Restart 情境驗證
    • 開 app → Open in Browser
    • 在 Wails 控制台按 Restart
    • 瀏覽器 tab 在 3-5 s 內自動 reload
    • Reload 後 server 是新的 port 也能正常連(因為 Next.js 是 static exportURL path 不變)
  3. 自動開瀏覽器R5-4 + R5-D2 + R5-D3
    • R5-D2分平台預設macOS/Windows 預設 AutoOpenBrowser=trueLinux 預設 false
    • R5-D3每次都開:只要 AutoOpenBrowser=true,每次 Start/Restart 成功都呼叫 OpenInBrowser("")取消原 v2.0 的 per-session-once 設計
    • 使用者在 Preferences 切換 → 立即持久化到 preferences.jsonatomic write
  4. Restart 期間 port 行為F-2 強制保留):
    • Restart 不允許 port fallbackstartWithPort(oldPort, forceMatch=true)
    • 舊 port 被佔用 → 進 Error state + 發 OS 通知
    • 正常 Restart → 新 server 用原 port → 瀏覽器 tab 偵測 boot-id 變 → reload → URL 原 port 仍有效 → 自動連上
    • 不會發生 v2.0 所述「port 變動後瀏覽器連不上」的情境

驗收條件

  • 關機情境Wails 關 → server 停 → 瀏覽器 tabWebSocket shutdown-imminent 秒內顯示 overlay(不是 15 s 了Minor 4
  • Restart 情境Wails 按 Restart3-5 s 內瀏覽器自動 reloadURL 原 port 仍有效F-2UI 正常
  • 首次開 appmacOS/Windows瀏覽器自動跳出新 tab
  • 首次開 appLinux瀏覽器自動開R5-D2 預設 false
  • Preferences 關閉後再開 app瀏覽器不自動開
  • 多次按 Restart:若 AutoOpenBrowser=true,每次 Restart 都會呼叫 OpenInBrowserR5-D3。OS 瀏覽器通常聚焦既有 tab 而不是開新 tab使用者可接受
  • R5-E 啟動進度面板Starting 期間顯示 6 階段進度,完成後淡出

Reviewer 檢查重點

  • app.go不存在 autoOpenedThisSession 欄位v2.0 已砍)
  • Restart 呼叫 OpenInBrowser 的行為有在 M8-9 手動測試紀錄中驗證
  • DefaultPreferences() 三平台分支在單元測試或手動測試中涵蓋
  • Restart 的 port 強制保留邏輯正確(錯的 mock 情境下會進 Error state 而非 fallback
  • boot-id 在 server 重啟後真的變(不會意外 memoize 舊值)
  • polling 的 consecutiveFailures 在 Restart 期間不會錯誤累積到 3Restart 約 3 s只有 0-1 次失敗)

M8-10端到端 build + smoke test

預估1 人天

負責 AgentDevOps Agent + Testing AgentQA

依賴M8-1 到 M8-9 全部完成

任務

  1. macOSmake clean-all && make dmgdist/visiona-local.dmg
    • 安裝到全新 mac或 VM
    • 8 核心驗收v2.1 擴充):
      • 打開 app 先看到啟動進度面板6 階段R5-E完成後淡出顯示控制台 UI(不是 splash / wizard / 白畫面)
      • 點 Open in Browser 後瀏覽器載入 Next.js Web UI
      • Web UI 沒有 URL tabsource-selector 清乾淨)
      • Web UI 沒有 Mock 模式切換Settings > Hardware 乾淨)
      • 點 Stop 後瀏覽器秒內看到 Offline OverlayWebSocket 廣播,不是 15 s 才顯示)
      • 手動殺 server process → 收到 macOS 原生通知 + 控制台 Error bannerR5-D1
      • R5-D2首次開 app 時 preferences.json 不存在macOS 預設自動開瀏覽器Linux 相對要反過來驗證)
      • 按 Restart 兩次,每次都會觸發 OpenInBrowserR5-D3砍掉 per-session-once
  2. Windows:在 Windows 機器上 make clean-all && make exe
    • 同樣 5 核心驗收
    • 注意R5-7 同意 M7 Windows 先不管,但這次 M8-10 要順帶驗證(做完再驗)
  3. Linux:在 Ubuntu 上 make clean-all && make appimage
    • 同樣 5 核心驗收
  4. LGPL 稽核:三個平台的 installer 安裝後,<install>/bin/ 下都有 ffmpeg + ffprobe + ffmpeg-COPYING.LGPLv3
  5. Installer size 對照
    • macOS .dmg預期 ~80-100 MBv1 是 220 MB砍 yt-dlp 35 MB + 砍 GPL ffmpeg 77 MB 換成 LGPL ~20 MB = 約 128 MB 降到 ~100 MB
    • Windows .exe預期 ~280-320 MBv1 是 ~380 MB
    • Linux .AppImage預期 ~240-280 MBv1 是 ~317 MB
  6. 回歸測試
    • Kneron KL520 / KL720 實機跑推論(若有硬體)
    • 5 種副檔名上傳影片
    • 批次影像上傳
    • Camerawebcam串流

驗收條件

  • 三平台 installer 全部能裝、能啟動、能進入控制台、能 Open Browser、能跑推論
  • LGPL 合規稽核:grep -r 'enable-gpl' vendor/ffmpeg/ 無輸出
  • 5 核心驗收 checklist 全綠
  • Installer size 在預期範圍內

Reviewer 檢查重點

  • 驗收 checklist 每一項都有實測截圖 / log
  • 回歸測試涵蓋 PRD v2 所列的核心 user story

3. 風險與緩衝

  • M8-3 macOS 自 build ffmpeg 可能踩坑configure flag 組合、pkg-config 環境、codesign預估 1.5 人天但可能實際花 2 人天。buffer 放在 M8-10 的 1 人天裡
  • M8-4b R5-E 6 階段 event emit 的非阻塞保證Wails v2 IPC 若慢,可能把啟動流程本身拖慢。需在 M8-4b 後做 mock 測試(每個階段插 dummy 延遲)確認 watcher goroutine 與 event emit 不互相阻塞
  • M8-4 OS 通知三平台實測R5-D1macOS 首次呼叫會彈出通知授權對話框Windows 沒裝 BurntToast 會 fallback msg *(某些版本可能 blockLinux 需要 libnotify-bin 已安裝。這三者都需要實機驗證,預估 0.3 天
  • M8-5 控制台 UI 的 dark mode 可能需要反覆調整:若實測後使用者不喜歡預設配色,留給 Design Agent 一輪調整(不在本次範圍內)
  • M8-4 高頻 log 壓測R-v2-3若 Wails v2 EventsEmit 真的 flaky要切 micro-batch 升級成 throttling + coalescing可能再多 0.5 人天

總工時重估

Milestone v2.0 v2.1 差異原因
M8-1 0.5 0.5
M8-2 0.5 0.5
M8-3 1.5 1.5
M8-4 1.5 2.0 +0.3 R5-D1 OS 通知、+0.2 Q4 7+1 秒 shutdown modal
M8-4b 1.0 新增R5-E 階段化啟動
M8-5 2.0 2.0
M8-6 0.5 0.5
M8-7 1.0 1.3 +0.3 Minor 4 WebSocket shutdown-imminent 訂閱
M8-8 0.5 0.5
M8-9 1.0 1.0
M8-10 1.0 1.2 +0.2 核心驗收項目從 5 個擴為 8 個
合計 10.0 12.0 +2 人天

加上 bufferM8-3 可能踩坑 + M8-4 高頻 log 壓測可能重構)~1 人天 → 建議 Orchestrator 對使用者回報 ~13 人天


4. 交棒給 Orchestrator 的清單

Milestone 觸發條件
M8-1 Backend + Frontend並行 使用者確認 TDD v2.1
M8-2 Backend + Frontend並行 同 M8-1
M8-3 DevOps + BackendmacOS host 可用) 同上,不依賴 M8-1/2
M8-4 Backend M8-1 + M8-2 砍完
M8-4b Backend + Frontendvanilla JS M8-4 完成(需要 ServerController + WebSocket hub
M8-5 Frontend M8-4b bindings + event schema 可用
M8-6 Frontend 可與 M8-1/2/5 平行
M8-7 Frontend M8-4 boot-id endpoint + WebSocket /ws/system 就緒
M8-8 Backend 可獨立進行
M8-9 Backend + Frontend M8-4 + M8-4b + M8-7 完成
M8-10 DevOps + Testing 全部完成

提醒:每個 milestone 完成後Orchestrator 啟動 Reviewer 審查,審查通過才進下一個 milestoneper 根目錄 CLAUDE.md 「強制 Review 規則」)。