依 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>
28 KiB
v2/milestone-plan.md — M8 開發 milestone 拆分
所屬:TDD v2 §2.7 版本:v2.1(2026-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 人天
負責 Agent:Backend Agent + Frontend Agent(同時進行,因為跨 Go + TS)
依賴:無
任務:
- 依
v2/deletions.md§1 刪後端 Go(video_source.go/camera_handler.go/router.go/deps/checker.go/main.go註解) - 依
v2/deletions.md§5 刪前端 TS(source-selector.tsx的 URL tab /camera-store.ts的startFromUrl/ i18n 4 個 key) - 依
v2/deletions.md§4.1 + §4.2 + §4.3 + §4.4 + §4.5 刪打包流程(Makefile / installer / bootstrap) - 刪除
/vendor/yt-dlp/目錄(若存在)
驗收條件:
go build ./...PASSgo test ./server/...PASSpnpm --dir frontend buildPASSgit 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 人天
負責 Agent:Backend Agent + Frontend Agent
依賴:無(理論上可與 M8-1 平行)
任務:
- 依
v2/deletions.md§2 + §3 刪 Go(兩個檔整檔刪、device/manager.go/camera/manager.go/config.go/main.go/app.go所有mockMode條件砍掉) - 依
v2/deletions.md§6 刪前端 Mock 切換 UI + 4 個 i18n key - 更新
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-devicesCLI flag 真的拿掉(server--help不再列) - 任何剩下的 test 檔案不再依賴 mockMode
M8-3:ffmpeg LGPL vendor 切換
預估:1.5 人天
負責 Agent:DevOps Agent(主)+ Backend Agent(支援 macOS build 環境)
依賴:無
任務:
- Windows / Linux(0.5 人天):依
v2/ffmpeg-lgpl.md§3 + §4 修改Makefile的 URL 與 vendor target,實測make vendor-ffmpeg-windows/vendor-ffmpeg-linux可成功 - macOS build script(1 人天):
- 依
v2/ffmpeg-lgpl.md§2.3 新增vendor-ffmpeg-macos-buildMakefile 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/ - 修改
.gitignore讓vendor/ffmpeg/macos/成為 git tracked - 修改
vendor-ffmpegtarget 改為「驗證 binary 存在且是 LGPL」
- 依
- Payload 階段同步(§7):三個平台的
payload-*target 改為 copyffmpeg + ffprobe + COPYING.LGPLv3,不再 copy yt-dlp(M8-1 會處理刪) - Installer 同步(§8):
installer/windows/visiona-local.iss新增 ffprobe 和 LGPL license 行installer/linux/build-appimage.sh迭代 tool 改為ffmpeg ffprobe
驗收條件:
make vendor-ffmpegPASS(macOS),binary size OK(見v2/ffmpeg-lgpl.md§10 驗收表)vendor/ffmpeg/macos/ffmpeg -version | grep -- --enable-gpl無輸出spctl --assess --verbose vendor/ffmpeg/macos/ffmpeg→ acceptedmake vendor-ffmpeg-windows+vendor-ffmpeg-linux可成功,檔案解出後 version 無--enable-gplmake 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-4:Server 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)
負責 Agent:Backend Agent(Go / Wails)
依賴:M8-1(避免 mockMode 欄位被砍後衝突)。實務上可與 M8-2 平行。
任務:
- 新增檔案(
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/WindowsAutoOpenBrowser=true、Linuxfalse)+ atomic write-rename(server-lifecycle.md§11.3)visiona-local/notify.go(~100 行)— R5-D1:三平台 OS 通知,macOSosascript display notification/ Linuxnotify-send/ Windows PowerShell BurntToast +msg *fallback,詳見server-lifecycle.md§10
- 修改
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 bindings(
v2/control-panel.md§4.2)
- 新增
- 修改
visiona-local/main.go:- 加
OnBeforeClosehandler shutdownGracePeriod5 s → 7 s(PM Q4 定案,不是 10 s)
- 加
- 修改
server/main.go+system_handler.go+router.go:- 新增 boot-id 生成(使用
crypto/rand16 bytes → hex,Architect Q3 決定,不引 google/uuid) - 新增
GET /api/system/boot-idendpoint - Minor 4:server 新增 WebSocket hub 廣播
server:shutdown-imminent的能力(新增/ws/systemendpoint 或擴充現有/ws/server-logs);Wails 的ctrl.Stop()在開始 SIGTERM 前透過 HTTP 或 server 內部 API 觸發此廣播 - 對齊 shutdown timeout:
server/main.go:166的shutdownFntimeout 10 s → 6 s(Wails 7 s 減 1 s,確保 server 先完成 cleanup)
- 新增 boot-id 生成(使用
驗收條件:
cd visiona-local && go build .PASScd 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=true;Linux 上回傳false curl http://127.0.0.1:<port>/api/system/boot-id回傳 JSON 含 bootId(hex 32 字元)- WebSocket
server:shutdown-imminent廣播:用 websocat 連/ws/system,按 Stop 後秒收到廣播
Reviewer 檢查重點:
- LogBuffer thread-safety(mutex 正確)
- 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 write(tmp + 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 新增)
負責 Agent:Backend Agent(Go / Wails)+ Frontend Agent(vanilla JS startup panel)
依賴:M8-4(需要 ServerController + bindings)
任務:依 v2/startup-pipeline.md(若該檔不存在則落在 control-panel.md §3.1 + server-lifecycle.md §2.1)
- 新增
visiona-local/startup_pipeline.go(~180 行):StartupPipelinestruct(含 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),emitstartup:stage-timeout/startup:errorevent- 4 個 event schema:
startup:progress/startup:stage-timeout/startup:error/startup:ready(見v2/startup-pipeline.md§1)
- 修改
visiona-local/app.go的startup(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()→ emitstartup:ready
- 在
- 修改 server 端:
pkg/ws(或對應的 WebSocket hub)新增OnClientConnectedcallback,讓 Wails 能收到「第一個 client 連上」的通知(透過 WebSocket 或 HTTP poll)- Wails 的
startup_pipeline.go訂閱此通知完成階段 6
- 失敗處理:任一階段失敗 →
pipeline.Fail(stage, err)→ctrl.setState(Error, ...)+sendCrashNotification+ emitstartup:error - 非阻塞:所有
EventsEmit呼叫都走 buffered channel 或 fire-and-forget goroutine,不阻塞啟動流程 - 前端(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:errorevent - 總時 > 60 秒情境:mock 每個階段都等 12 秒 → 60 秒後進 Error state(watcher 總時檢查有效)
- 日常啟動(非首次):~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-5:Wails 控制台 vanilla UI 改寫
預估:2 人天
負責 Agent:Frontend Agent(vanilla JS)
依賴:M8-4(需要 bindings + events)
任務:
- 改寫
visiona-local/frontend/index.html成控制台 layout(v2/control-panel.md§3) - 改寫
visiona-local/frontend/app.js成控制台主程式(§5) - 改寫
visiona-local/frontend/style.css新增 status card / log panel / action bar / preferences 樣式,並加 light/dark mode CSS variables - 新增 components:
components/status-card.jscomponents/log-panel.js(含 virtual scroll lite + batch render)components/action-bar.js(含 disable 矩陣)components/preferences.js
- 新增 i18n:
i18n/en-US.json/zh-TW.jsoni18n/loader.js
- 新增 icons:
icons/*.svg× 6
驗收條件:
- Wails dev 模式下開 app:
- ✅ 看到控制台 UI(status / log / actions / preferences),不是 splash / blank / Next.js
- ✅ Start 按鈕能啟動 server,狀態卡片變 Running 綠色 badge
- ✅ Log panel 即時顯示 server stdout(看到 Gin log)
- ✅ Stop 按鈕能停 server,badge 變灰
- ✅ 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,打開後看到控制台 UI(M7-B 教訓)
Reviewer 檢查重點:
- vanilla JS 沒引入任何 npm 依賴(
package.json不存在於 visiona-local/frontend/) - Log panel 在高頻(1000 行/秒)下不會 freeze UI(batch render 有效)
- i18n loader SSR-safe(不適用,但 Wails 沒 SSR 概念)
- icons/ 下的 SVG 符合 inline 使用的簡潔規則(viewBox + single path)
- Action bar 在每個 state 下 button enable/disable 正確(disable 矩陣全部驗證)
M8-6:Web UI source-selector + 副檔名擴充
預估:0.5 人天
負責 Agent:Frontend Agent
依賴:無(與其他 milestone 平行)
任務:
frontend/src/components/camera/source-selector.tsx依v2/deletions.md§5.1 移除 URL tab + mode toggle- 把
accept=".mp4,.avi,.mov"改為accept=".mp4,.avi,.mov,.mpeg,.mpg" - i18n 新增
videoFormatskey 或改既有mp4AviMov - 後端同步:
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-7:Web UI Server Offline Overlay
預估:1 人天
負責 Agent:Frontend Agent
依賴:M8-4(需要 server 端 boot-id endpoint)
任務:依 v2/web-ui-offline-overlay.md §4 的 7 個檔案清單:
- 新增
frontend/src/stores/system-store.ts - 新增
frontend/src/hooks/use-boot-id-watcher.ts - 新增
frontend/src/components/server-offline-overlay.tsx - 新增
frontend/src/components/boot-id-watcher-mount.tsx - 修改
frontend/src/app/layout.tsx(掛 overlay + watcher) - 修改
frontend/src/lib/i18n/{types,zh-TW,en}.ts(新增 serverOffline 區塊)
驗收條件:
pnpm --dir frontend buildPASS(含 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的 cleanup(useEffect return)正確 cancelAbortSignal.timeout(3000)在舊瀏覽器相容性(若需支援 Chrome < 103 則 fallback)- Overlay 元件 a11y(
role="alertdialog"+aria-labelledby) - i18n 新增 key 三個檔同步(types / zh-TW / en)
M8-8:CORS + origin check middleware
預估:0.5 人天
負責 Agent:Backend Agent
依賴:無
任務:依 v2/cors-security.md §4 + §5:
- 覆寫
server/internal/api/middleware.go(白名單版) - 新增
server/internal/api/ws/origin.go - 修改所有 WS handler 的 upgrader CheckOrigin
- 新增
server/internal/api/middleware_test.go單元測試(TestIsAllowedOrigin) - 在
router.go掛requireSameOriginOrNoOriginmiddleware 到/api/*
驗收條件:
go test ./server/internal/api/... -run TestIsAllowedOriginPASSv2/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-Tokenheader 保留(relay 早已砍) - WS upgrade 在非白名單 origin 下回 403,不是靜默失敗
- Middleware 套用順序(middleware.go 的 CORSMiddleware 要在 requireSameOriginOrNoOrigin 之前)
M8-9:Boot-ID 端到端整合 + Restart 重連
預估:1 人天
負責 Agent:Backend Agent + Frontend Agent
依賴:M8-4 + M8-7
任務:
- 整合驗證:M8-4 的 server 端 boot-id API + M8-7 的前端 polling 串起來
- Restart 情境驗證:
- 開 app → Open in Browser
- 在 Wails 控制台按 Restart
- 瀏覽器 tab 在 3-5 s 內自動 reload
- Reload 後 server 是新的 port 也能正常連(因為 Next.js 是 static export,URL path 不變)
- 自動開瀏覽器(R5-4 + R5-D2 + R5-D3):
- R5-D2(分平台預設):macOS/Windows 預設
AutoOpenBrowser=true;Linux 預設false - R5-D3(每次都開):只要
AutoOpenBrowser=true,每次 Start/Restart 成功都呼叫OpenInBrowser("")— 取消原 v2.0 的 per-session-once 設計 - 使用者在 Preferences 切換 → 立即持久化到
preferences.json(atomic write)
- R5-D2(分平台預設):macOS/Windows 預設
- Restart 期間 port 行為(F-2 強制保留):
- Restart 不允許 port fallback(
startWithPort(oldPort, forceMatch=true)) - 舊 port 被佔用 → 進 Error state + 發 OS 通知
- 正常 Restart → 新 server 用原 port → 瀏覽器 tab 偵測 boot-id 變 → reload → URL 原 port 仍有效 → 自動連上
- 不會發生 v2.0 所述「port 變動後瀏覽器連不上」的情境
- Restart 不允許 port fallback(
驗收條件:
- 關機情境(Wails 關 → server 停 → 瀏覽器 tab):WebSocket shutdown-imminent 秒內顯示 overlay(不是 15 s 了,Minor 4)
- Restart 情境(Wails 按 Restart):3-5 s 內瀏覽器自動 reload,URL 原 port 仍有效(F-2),UI 正常
- 首次開 app(macOS/Windows):瀏覽器自動跳出新 tab
- 首次開 app(Linux):瀏覽器不自動開(R5-D2 預設 false)
- Preferences 關閉後再開 app:瀏覽器不自動開
- 多次按 Restart:若
AutoOpenBrowser=true,每次 Restart 都會呼叫OpenInBrowser(R5-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 期間不會錯誤累積到 3(Restart 約 3 s,只有 0-1 次失敗)
M8-10:端到端 build + smoke test
預估:1 人天
負責 Agent:DevOps Agent + Testing Agent(QA)
依賴:M8-1 到 M8-9 全部完成
任務:
- macOS:
make clean-all && make dmg→dist/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 tab(source-selector 清乾淨)
- ✅ Web UI 沒有 Mock 模式切換(Settings > Hardware 乾淨)
- ✅ 點 Stop 後瀏覽器秒內看到 Offline Overlay(WebSocket 廣播,不是 15 s 才顯示)
- ✅ 手動殺 server process → 收到 macOS 原生通知 + 控制台 Error banner(R5-D1)
- ✅ R5-D2:首次開 app 時
preferences.json不存在,macOS 預設自動開瀏覽器(Linux 相對要反過來驗證) - ✅ 按 Restart 兩次,每次都會觸發
OpenInBrowser(R5-D3,砍掉 per-session-once)
- Windows:在 Windows 機器上
make clean-all && make exe- 同樣 5 核心驗收
- 注意:R5-7 同意 M7 Windows 先不管,但這次 M8-10 要順帶驗證(做完再驗)
- Linux:在 Ubuntu 上
make clean-all && make appimage- 同樣 5 核心驗收
- LGPL 稽核:三個平台的 installer 安裝後,
<install>/bin/下都有ffmpeg + ffprobe + ffmpeg-COPYING.LGPLv3 - Installer size 對照:
- macOS .dmg:預期 ~80-100 MB(v1 是 220 MB,砍 yt-dlp 35 MB + 砍 GPL ffmpeg 77 MB 換成 LGPL ~20 MB = 約 128 MB 降到 ~100 MB)
- Windows .exe:預期 ~280-320 MB(v1 是 ~380 MB)
- Linux .AppImage:預期 ~240-280 MB(v1 是 ~317 MB)
- 回歸測試:
- Kneron KL520 / KL720 實機跑推論(若有硬體)
- 5 種副檔名上傳影片
- 批次影像上傳
- Camera(webcam)串流
驗收條件:
- 三平台 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-D1):macOS 首次呼叫會彈出通知授權對話框;Windows 沒裝 BurntToast 會 fallback
msg *(某些版本可能 block);Linux 需要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 人天 |
加上 buffer(M8-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 + Backend(macOS host 可用) | 同上,不依賴 M8-1/2 |
| M8-4 | Backend | M8-1 + M8-2 砍完 |
| M8-4b | Backend + Frontend(vanilla 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 審查,審查通過才進下一個 milestone(per 根目錄 CLAUDE.md 「強制 Review 規則」)。