visionA/local-tool/.autoflow/03-design/reviews/architect-review-of-design-spec-v2.md
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

35 KiB
Raw Blame History

Architect 交叉審閱 Design Spec v22026-04-14

審閱者Architect Agent 對象:03-design/design-spec-v2.md + 03-design/v2/*.md 基準:04-architecture/TDD-v2.md + 04-architecture/v2/*.md


摘要

  • 結論有條件通過(需小改)
  • Major2A-4 Settings 儲存 spec + A-5 Linux 預設值)
  • Minor12
  • 是否阻擋進開發不阻擋Major 都是 < 10 行 spec 改動,可在 M8-4/M8-5 前補

A. Design ↔ TDD 實作一致性

Design v2 TDD v2 一致性 備註
control-panel.md5 態 + 元件 + i18n control-panel.md + server-lifecycle.md §5 ⚠️ 部分 A-1A-5
server-offline-overlay.mdrole=alertdialog + focus trap + 3s active polling web-ui-offline-overlay.md ⚠️ 間隔差 A-6
source-selector-update.md(砍 URL + .mpeg/.mpg + i18n deletions.md §1 §5 大致 A-7
first-run-update.md2 步 + 硬體可略過) 未描述(屬 Next.js onboarding 無衝突
settings-update.mdauto-open toggle control-panel.md §4.1 + server-lifecycle.md §2.1 ⚠️ 命名差 A-4

A-1Minor狀態名大小寫 Design 用首大寫 / TDD 用全小寫 — 語意同i18n key 對齊即可。不需改。

A-2MinorDesign 3 顆 PrimaryStop/Restart 收 Manage dropdownvs TDD 6 顆扁平。binding 層一致Design 是 UI 組合方式。M8-5 實作時 Manage dropdown item 呼叫 TDD 定義的 StopServer()/RestartServer() bindings。我會在 M8-5 前補到 TDD control-panel.md §3 註記。

A-3MinorLog 行數上限Design 1000 / TDD 2000。決案採 2000Go ring buffer 容量常數,~400KB 記憶體可忽略)。Design Spec §4.4 需改 1000 → 2000(含 Footer 的 Lines: {current} / 1000)。

**A-4Major**Settings 儲存 spec 不一致

  • Design Spec settings-update.md §2.2settings.json + 「走 Wails 既有 settings store」
  • TDD v2 control-panel.md §4.1preferences.go + <dataDir>/preferences.json
  • 問題Wails v2 沒有內建 settings storeDesign 誤解);現況無任何 settings 存檔機制,需新建
  • 決案:採用 TDD 的 preferences.json + <dataDir>/ 路徑 + 新建 visiona-local/preferences.go
  • Design 需改§2.2 檔名改 preferences.json,刪掉「走 Wails 既有 settings store」一句

**A-5Major**R5-D2 Linux 預設 OFF 兩份 spec 都未落地

  • Design 寫「預設值ON」三平台一致
  • TDD Preferences{OpenBrowserOnStart bool} 預設填 true
  • 決案:新增 DefaultPreferences()runtime.GOOS 回傳:
    func DefaultPreferences() Preferences {
        return Preferences{OpenBrowserOnStart: runtime.GOOS != "linux"}
    }
    
  • Design 需改§2.2「預設值」那格加註「macOS/Windows=ONLinux=OFFxdg-open 極簡 WM 可能失敗)」
  • TDD 需改control-panel.md §4.1DefaultPreferences()(我負責 M8-4 前補)

A-6MinorOffline Overlay 間隔不同

  • Design10s 正常 / 失敗 2 次 / overlay 期間 3s
  • TDD5s 正常 / 失敗 3 次(無 active 間隔切換)
  • 決案採 Design10s 省 CPU3s active 更貼合 R5-D3 重啟救援體驗)
  • TDD 需改web-ui-offline-overlay.md §3.1 POLL_INTERVAL_MS=10000 / FAILURE_THRESHOLD=2 + overlay active 切換至 3000(我負責 M8-7 前補)

A-7Minori18n 刪除清單微差

  • Design 砍 camera.uploadFileTDD 漏列)
  • TDD 砍 cannotOpenVideoUrlDesign 漏列)
  • M8-1 執行者取聯集即可

B. R5-D 補充決策技術落地

B-1 R5-D1 Server 崩潰保留 OS 原生通知

  • 現況 visiona-local/app.go:240-272 showNativeError() 已有三平台實作osascript display dialog / PowerShell MessageBox / zenity / kdialog但這是reportFatal() 用的 modal dialog
  • TDD v2 control-panel.md §4.7 把 watchServer 3 次失敗從 reportFatal 改為 setState(Error)同時等於砍掉 OS 通知 → 違反 R5-D1
  • 決案Error state 時並存兩件事:(1) 控制台 bannerDesign §6(2) 新的 non-blocking toast notification(不是 modal
    • macOSosascript -e 'display notification "..." with title "..."'toast非 dialog
    • Windows優先 wailsRuntime.SendNotification(見 frontend/wailsjs/runtime/runtime.d.ts:275-310 已自動生成fallback msg * 命令列
    • Linuxnotify-sendfallback zenity --notification
  • 新增 visiona-local/notify.gosendCrashNotification(title, body)
  • 保留 showNativeError() / reportFatal() 僅給 startup 致命錯誤用data dir 建不起來等)
  • TDD 需改control-panel.md §4.7sendCrashNotification 呼叫(我負責 M8-4 前補)
  • Design 建議加§6.2/§6.3 加一句「Error state 時另發 non-blocking OS notificationR5-D1

B-2 R5-D2 Linux 預設 OFF — 見 A-5。

B-3 R5-D3 每次啟動都自動開

  • TDD control-panel.md §4.6if prefs.OpenBrowserOnStart && !autoOpenedThisSession
  • autoOpenedThisSession 是 per-App-process flag每次 Wails 新啟動會重建 → 每次啟動都會開
  • Restart Server同一 App process 內)不會重開瀏覽器
  • 技術落地正確
  • Design 建議改control-panel.md §7.1 第 5 步「[首次 / Settings 為 ON]」→「[每次 / Settings 為 ON]」

C. 體驗 ↔ 技術紅線

C-1 OnBeforeCloseDesign Footer 持久提示取代 modal 確認 ↔ TDD server-lifecycle.md §7 OnBeforeClose return false 一致。但 Linux AppImagei3/xmonadflaky 的 edge case TDD §10 已記錄M8-10 驗收時實測。

C-2 Offline Overlay focus trapDesign 要求 Tab 只能在「重試 / 了解更多」間循環TDD web-ui-offline-overlay.md §4.4 沒 focus trap 實作決案M8-7 Frontend Agent 手動實作(useEffect 裡 onKeyDown 擋 Tab + 強制 focus~15 行,不引入 react-focus-lock)。SSR 相容性'use client' + Zustand + effect 只在 client 跑, 無問題。

C-3 3s active polling 對 log pump 的壓力0.33 行/秒 vs R-v2-3 的推論 30-100 行/秒差兩個量級, 無壓力。額外建議server Gin logger 加入 SkipPaths: []string{"/api/system/boot-id"} 避免 boot-id probe 洗版控制台 log panel。我負責 M8-9 前補到 TDD。

C-4 Wails 控制台 i18n

  • Design Spec §9.1 列 30+ keynamespace control.*
  • TDD §6.1 範例只 ~15 keynamespace statusCard.*/actions.*
  • 決案:以 Design 為準(control.* 更語意化,對齊 Web UI 風格。M8-5 執行者依 Design Spec §9.1 填 visiona-local/frontend/i18n/{zh-TW,en-US}.json

C-5 Dark ModeDesign 要求跟隨系統 + 與 Web UI 共用 tokenTDD §2 用 CSS vars + prefers-color-scheme 方向一致。建議做法M8-5 執行者直接從 frontend/src/app/globals.css 複製 :root + [data-theme='dark'] token block 到 visiona-local/frontend/style.css,不重造一份。


D. Architect 7 懸念決策

Q1 NewVideoSourceFromURL 呼叫者grep 驗證):

  • camera_handler.go:435StartFromURL 內(即將砍)
  • camera_handler.go:731 在 seek handler 的 if h.videoIsURL 分支;砍 StartFromURLh.videoIsURL 永遠 false → dead branch
  • 決案NewVideoSourceFromURL / NewVideoSourceFromURLWithSeek / h.videoIsURL field 一起砍。M8-1 執行時套用。我負責補到 TDD deletions.md §1.2 末段。

Q3 uuid vs crypto/randcrypto/rand~10 行 hex encode不引入 google/uuid 依賴。TDD server-lifecycle.md §9.1/§10 Q3 以此為準。

Q4 shutdownGracePeriod 5s vs 10sblocked-on-pm-review(我的意見:建議 10s 對齊 server shutdownFn timeout使用者等 10s 是罕見 edge case

Q5 navigator.language fallback

const raw = (navigator.language || navigator.userLanguage || 'en').toLowerCase();
if (raw.startsWith('zh')) return 'zh-TW';
if (raw.startsWith('en')) return 'en-US';
return 'zh-TW'; // 預設繁中visionA 團隊內部工具)

TDD §6.2/§8 Q4 以此為準。

Q6 window.close() 限制blocked-on-design-review我的意見Chrome/Safari/Edge 禁止非 window.open() 開啟的 tab 呼叫 window.close()。建議 Design 把「關閉這個頁面」按鈕改為「我知道了」純視覺收斂,或改為提示「請手動關閉此分頁」)

Q7 Preferences JSON atomic writewrite-rename pattern

os.WriteFile(path+".tmp", data, 0o644); os.Rename(path+".tmp", path)

POSIX/Windows 都原子。TDD §8 Q3 以此為準。


E. PM 3 技術懸念回答

§11-1 Settings 資料落地位置

  • 檔名 preferences.json,路徑 <dataDir>/preferences.json
    • macOS ~/Library/Application Support/visiona-local/preferences.json
    • Windows %APPDATA%\visiona-local\preferences.json
    • Linux ~/.local/share/visiona-local/preferences.json
  • migrateOldDataDirs()/ensureDataDir() 算出的路徑(visiona-local/app.go:103-155
  • JSON 格式:{"openBrowserOnStart": true}
  • 原子寫入write-renameD-Q7
  • 讀取失敗 fallbackDefaultPreferences()(平台差異,見 A-5

§11-2 US-1 AC-1.3 10 秒預算

步驟 估時
Wails 載入 + OnStartup 0.3-0.5s
migrate/lock/ipc/seed 0.1-0.2s
ensurePythonRuntime 0.2-0.5s
pickPort + spawn server 0.05-0.1s
Server cold start 1.5-2.5s
waitHealthy 首次成功 0.5-1.5s
OpenInBrowser + OS open 0.3-0.8s
Next.js SPA 載入 0.8-1.5s
boot-id + WS ready 0.3-0.5s
樂觀 ~4.1s
悲觀Intel Mac + system Python ~8.1s
Linux AppImage 首次解壓 +1-2s,仍達標
Windows + Defender 掃描 +2-3s,最壞 ~11s可能超時
  • 風險Windows 首次啟動可能超過 10s。建議 M8-10 驗收實測,超時則 PRD AC-1.3 放寬到 12s

§11-3 idle RAM ≤ 450 MB

程序 v1 v2
Wails shell 150-200 MB 120-160 MB(無 Next.js bundle
Go server 80-120 MB 70-100 MB(砍 Mock + yt-dlp
Python sidecar 150-250 MB 0 MB 若 on-demand150-250 MB 若 always spawn
Browser tab (Next.js Web UI) 180-240 MB(新項目)
樂觀 ~370 MB
悲觀 ~500 MB 超 50 MB
  • 建議PRD v2 §NFR 明示「450 MB 不含 browser tab,只含 Wails + Go server + Python sidecarbrowser tab 視為使用者瀏覽器自己的資源」—— 這個 clarify 符合常識且達標
  • 或:放寬到 ≤ 550 MB
  • 另需確認v1 visiona-local/app.go:441ensureDriverInstalled 是否在無硬體時仍 spawn python。若是v2 建議改為 on-demand 節省 idle RAM

F. 技術衝突

F-1 CORS ↔ 瀏覽器 tab origin

  • Wails「Open in Browser」→ http://127.0.0.1:<port> → 白名單內
  • 使用者手改 localhost → 白名單也接受
  • Same-origin fetch不送 OriginCORSMiddleware() 第一個 if 放行
  • 無衝突

F-2 Restart Server port 保留Major 隱含議題):

  • TDD v2/server-lifecycle.md §3Restart 後重新 pickPort(3721),可能 fallback 到 3722
  • Boot-ID 機制假設 tab URL 的 port 不變 → Restart 後 port 變 → 舊 tab window.location.reload() 仍連舊 port → overlay 永久卡死
  • 決案ServerController.Restart() 強制保留舊 port,若 old port 不可用 → 進 Error state 並發 OS notification「port 被占用」,不 fallback。cold start 則仍允許 3721→3722 fallback
  • TDD 需改server-lifecycle.md §3 新增 Restart 同 port 規則(我負責 M8-4 前補)
  • Design Spec control-panel.md §7.3 的 fallback 情境只適用 cold start不影響 Design但 Frontend Agent 需知

F-3 Boot-ID 機制在「關 Wails = 結束 server」場景

  • server 沒了 → boot-id 不會變 → 由 overlay 接手(連續失敗 → 顯示)
  • 使用者重開 app 若走 cold start port fallback 到新 port → 舊 tab 的 URL3721連不上新 port3722→ overlay 永卡;使用者需從新 Wails 控制台 Open in Browser 開新 tab
  • 設計正確TDD §2.3 時序已暗示,不需改

G. 臆測 / 超範圍

G-1 Export log 按鈕Design §4.5TDD 沒對應 binding。技術可行wailsRuntime.SaveFileDialogM8-5 補 ExportLog(path) error binding。我負責補到 TDD §4.2。

G-2 Copy 按鈕Design §4.5):直接用 navigator.clipboard.writeText() 瀏覽器 APIWails WebView 支援。不需新 binding

G-3 Report 按鈕Design §6.2 Error banner需要 GitHub Issue URL目前沒公開 repo降級M8-5 若 PM 沒提供 URL → 不實作 Report 按鈕Error banner 只留 Restart + View details。Design 需加§6.2 hold 註記「Report 按鈕待 PM 提供 repo URL暫不實作」。

G-4 Log rotate 7 天/10MBDesign §4.4TDD 沒 rotate 實作v1 也無。降級為 append 模式,不做 rotaterotate 需要 lumberjack.v2 或自刻定時掃描 + size 比較,非 M8 scopeDesign 需改§4.4 「rotate 7 天/10MB」改為「append 模式;使用者可 Clear Logs 清畫面、Open Log Folder 手動刪檔」。未來 M9+ 再加。


H. 問題清單

Major阻擋需修正

# 位置 問題 修正
M-1 Design settings-update.md §2.2 檔名 settings.json + 「走 Wails settings store」誤解 preferences.json,刪掉 Wails settings store 那句
M-2 Design settings-update.md §2.2 + TDD control-panel.md §4.1 R5-D2 Linux 預設 OFF 未落地 Design 加「macOS/Windows=ONLinux=OFF」TDD 加 DefaultPreferences() 依 GOOS

Minor不阻擋

# 位置 處理者
m-1 Design control-panel.md §4.4 + Footer 1000 → 2000
m-2 TDD web-ui-offline-overlay.md §3.1 10s/2 次/3s active我 M8-7 前補)
m-3 M8-1 執行者 i18n 刪除清單取聯集
m-4 TDD control-panel.md §4.7 + Design control-panel.md §6 Error state 補 OS notification我 M8-4 前補 TDD
m-5 Design control-panel.md §7.1 第 5 步 「首次」→「每次」對齊 R5-D3
m-6 M8-7 Frontend Agent Offline Overlay focus trap 實作
m-7 TDD server-lifecycle.md §9 Gin SkipPaths + crypto/rand我 M8-9 前補)
m-8 M8-5 執行者 i18n 以 Design §9.1 為準
m-9 TDD server-lifecycle.md §3 Restart 強制同 port我 M8-4 前補)
m-10 TDD control-panel.md §4.2 ExportLog binding我 M8-5 前補)
m-11 Design control-panel.md §6.2 Report 按鈕 hold 註記
m-12 Design control-panel.md §4.4 Log rotate 降級為無 rotate

I. 結論

有條件通過 — Design Spec v2 方向正確,與 TDD v2 無不可修復衝突。

Design Agent 必改Major + 關鍵 Minor

  • settings-update.md §2.2M-1 + M-2
  • control-panel.md §4.4m-1 + m-12
  • control-panel.md §6.2m-4 + m-11
  • control-panel.md §7.1m-5

TDD 必改我負責M8- 執行前補)*

  • control-panel.md §4.1DefaultPreferences() 平台差異)
  • control-panel.md §4.2ExportLog binding
  • control-panel.md §4.7sendCrashNotification + 新增 notify.go
  • control-panel.md §3Manage dropdown 註記)
  • server-lifecycle.md §3Restart 同 port
  • server-lifecycle.md §9Gin SkipPaths + crypto/rand
  • web-ui-offline-overlay.md §3.110s/2 次/3s active
  • deletions.md §1.2videoIsURL field 直接砍)

Q 決案狀態Q1 Q3 Q4 PM Q5 Q6 Design Q7

PM 3 技術懸念

  • §11-1 preferences.json @ <dataDir>/write-rename
  • §11-2 10 秒樂觀 ~4s / 悲觀 ~8s / Windows 邊界 ~11sM8-10 驗收時實測,可能需放寬到 12s
  • §11-3 ⚠️ idle RAM 需 clarify「不含 browser tab」或放寬到 550 MB另需確認 Python sidecar 是否 on-demand

不阻擋進開發:所有修正都是 < 10 行 spec 改動,可在 M8-4 / M8-5 執行前 1-2 小時內完成。


審閱日期2026-04-14 下一步:交 Orchestrator 彙整三方 + R5-D 對齊 + Q4/Q6 收斂 → 使用者最終確認 → M8-1


Architect 第二輪審閱 Design Spec v2.12026-04-14

對象:design-spec-v2.md v2.1 索引 + v2/settings-update.md v2.1 + v2/control-panel.md v2.1 + 新檔 v2/startup-progress.md v2.1 基準TDD v2.1 v2/startup-pipeline.md518 行)+ 第一輪審閱結論

摘要

  • 總結論通過,需小改3 個 Minor都在 TDD 側補齊)
  • 第一輪 Major × 2 / Minor × 12 修復Major 2/2 修好Minor 12/12 修好或已落到 TDD/執行者待辦
  • R5-E startup-progress.md 技術可行性可行event schema 7 成對齊,有 3 處 TDD 側需補小改
  • 是否阻擋進 M8 開發不阻擋TDD 小改可在 M8-4b 執行前 30 分鐘內補完

A. 第一輪 Major 修復檢查

# 第一輪意見 v2.1 現況 結果
M-1 Settings 落地檔案 preferences.json + 刪 Wails settings store settings-update.md §2.2L48-56已明文「preferences.json @ <dataDir>/」+「Go server 端負責讀寫(非 Wails 內建機制 — Wails v2 沒有 settings store」+「write-rename pattern」全檔 grep 無 settings.json 殘留無「Wails settings store」殘留 修好
M-2 R5-D2 Linux 預設 OFF 加「macOS/Windows=ONLinux=OFF」依 GOOS settings-update.md §2.2L51「macOS/Windows = ONLinux = OFF」+ L55「DefaultPreferences()runtime.GOOS」+ §2.2 Linux 首次使用說明L59-63+ §7 遷移策略L180-190全部落地 修好

B. 第一輪 Minor 修復檢查

# 第一輪意見 v2.1 現況 結果
m-1 Log 上限 1000→2000 control-panel.md §4.4L152「最大行數 2000」、§4.6 FooterL186Lines: {current} / 2000
m-2 Offline Overlay 10s/2 次/3s active → TDD 側 非 Design 修,我負責 M8-7 前補 TDD 我的 TODO未阻擋
m-3 i18n 刪除清單聯集 → 執行者 M8-1 執行者責任 執行者 TODO
m-4 Error state OS notification control-panel.md §6.3L269-275「R5-D1 OS 原生通知並存」完整三平台實作表Design Diff §12-6L459
m-5 §7.1 「首次」→「每次」 control-panel.md §7.1L310「5. 【每次 / Settings 為 ONmacOS/Windows 預設Linux 預設 OFF】」L331「每次啟動每次 Wails App process 新啟動)都會跑完整 6 階段流程」
m-6 Offline Overlay focus trap M8-7 Frontend 責任 執行者 TODO
m-7 Gin SkipPaths + crypto/rand → TDD 側 我負責 M8-9 前補 我的 TODO
m-8 i18n 以 Design §9.1 為準 Design control-panel.md §9L387-408完整 control.* 清單
m-9 Restart 強制同 port → TDD 側 我負責 M8-4 前補 我的 TODO
m-10 ExportLog binding → TDD 側 我負責 M8-5 前補 我的 TODO
m-11 Report 按鈕 hold control-panel.md §6.2L265【hold】 現階段先不實作」;startup-progress.md §3.7L221「Report Issue 【hold】
m-12 Log rotate 降級 control-panel.md §4.4L152-162「無落地寫檔in-memory ring buffer」+「為什麼取消落地寫檔與 rotate」整段說明

修復統計Design 側 8/8 已修m-1、m-4、m-5、m-8、m-11、m-12 + M-1、M-2TDD 側 4 個由我自己負責m-2、m-7、m-9、m-10執行者側 2 個m-3、m-6無 Design 側遺漏


C. R5-E startup-progress.md 技術可行性(重中之重)

C-1 Wireframe DOM/CSS 難度

startup-progress.md §2L25-101三個 wireframe 全部 text-basedStageItem 結構§3.3 L141-154明確示範 vanilla HTML + CSS class data-state可直接對齊 TDD startup-pipeline.md §6L407-491的 vanilla JS 實作。無 framework-specific 要求

C-2 6 階段定義對應 TDD §3 階段表

# Design label TDD §3 Go 實作點 對應
1 初始化控制台 app.go:startup 首行 → seedUserDataDir() 返回
2 檢查 Python 執行環境 startServerV2ensurePythonRuntime() 返回
3 啟動本機伺服器 waitHealthy(port, 30s) 返回
4 偵測 Kneron 裝置 GET /api/devices 第一次 response
5 開啟瀏覽器 OpenInBrowser("") 命令 return
6 等待 Web UI 連線 WebSocket hub OnClientConnected 首次

完全對齊。Design §8 的訊號來源L361-367和 TDD §3 表格用詞一致。

C-3 Event schema 一致性(發現 3 個小衝突)

Design startup-progress.md §9L386-393預設的 event 名:

startup:stage {id, state, errorMessage?}
startup:complete / startup:error

TDD startup-pipeline.md §1L28-84定義的 event 名:

startup:progress   (StartupProgressEvent: stage, totalStages, labelKey, status, startedAt)
startup:stage-timeout (StartupStageTimeoutEvent: stage, softTimeoutSeconds)
startup:error      (StartupErrorEvent: stage, error, cause)
startup:ready      (無 payload)

衝突 1:事件名 startup:stage vs startup:progress 不一致。決案:採 TDD 的 startup:progress(更語義化;stage 單字太模糊。Design Agent 不必改 startup-progress.md §9備忘性質由 M8-4b / M8-5 執行者以 TDD 為準。

衝突 2Design StageState 列舉§8 L335-341pending | running | running-slow | done | failed | skippedTDD StartupProgressEvent.Status§1.1 L41只定義 pending | running | completed | failed

差異分析:

  • running-slow — Design 明確標註「UI 派生狀態」L338 註解),由前端根據 now - startedAt > 20s 計算Go 端不需 emit → 無衝突
  • done vs completed — 語義同,用詞差。決案Go 端 emit completed,前端 Design class name 用 done 即可CSS 層面對應)
  • skipped Go 端完全沒有 — 這是 TDD 側 Minor 問題,要補

衝突 3soft timeout 事件名不同。Design §9 預設「前端自己 timer 檢查 20 秒 → 派生 running-slowTDD §1.2 定義「Go 端 watcher goroutine emit startup:stage-timeout」。兩種實作都可行,決案以 TDD 為準(後端 source of truth避免兩邊各自計時導致時間漂移Design 前端僅負責接 startup:stage-timeout event 更新 UI。

小結:事件協議 Design 預設是草稿TDD 是 ground truthM8-4b/M8-5 執行者以 TDD 為準即可,Design Agent 無需修 startup-progress.md §9。但 TDD 需補 skipped status(見 E 節)。

C-4 Timeout UI 觸發時機對齊

  • 20 秒 soft timeoutDesign §3.6L189-205stage.state === 'running' && (now - stage.startedAt) > 20_000msTDD §4 watcher goroutineL332-346sinceStage > startupSoftTimeout + softTimeoutEmitted flag 確保只 emit 一次。 觸發時機一致Go emit event → 前端收到顯示 hint
  • 60 秒 hard timeoutDesign §3.7L207-224「任一階段 failed 或總計 > 60 秒 → Error mode」TDD §4 watcherL325-330sinceTotal > startupHardTimeoutFail(cur, ...) + emitError(cur, ..., "total-timeout") 一致

C-5 無障礙可行性

  • role="progressbar" + aria-valuemin="0" + aria-valuemax="6" + aria-valuenow — vanilla HTML <section> 直接標即可
  • aria-live="polite" 搭配 .sr-only div — Design §6L281已設計vanilla JS textContent 更新即可觸發 SR 讀出
  • ⌘0 / Ctrl+0 focus + Esc — Design §6L283vanilla JS addEventListener('keydown') 攔截即可
  • prefers-reduced-motion — CSS media queryvanilla CSS 支援

可行性100%。不需引入任何 third-party library。

C-6 Linux / Toggle OFF 分支(重要

Design 規格§4.1 L244-256

  • 階段 5pending → skipped(不經過 running),顯示 ⏭ icon
  • 階段 6pending → runningdescription 改 manualHint不套 20 秒 retry hint
  • 當使用者手動點 Open in Browser 並建立 WebSocket → done

TDD §3 表L117階段 5「若 AutoOpenBrowser=false 則立即 complete 跳過」— TDD 是 completedDesign 是 skipped,語義不同。Design 要顯示「跳過(依偏好設定)」⏭ icon不是綠勾

TDD 也沒有處理階段 6 不套 soft timeout 的情境§4 watcher 對所有階段一視同仁Toggle OFF 時階段 6 會在 20 秒後觸發 startup:stage-timeout,前端會顯示「正在重試...」—— 這是錯的,因為在等人為動作。

兩個問題都是 TDD 側需補(見 E 節 E-2 / E-3

C-7 i18n key 30+ 條

Design §7L291-329列出 27 個 i18n keynamespace 一致用 startup.*。TDD §2L87-103只列 11 個 key 作為骨架,註明「文案由 Design Spec v2.1 敲定」。

key 名稱微差

  • Designstartup.panel.title / TDDstartup.title — 統一採 Design 的 startup.panel.title(更完整的 namespace 樹狀結構)
  • Designstartup.timeout.message / TDDstartup.retrying — 統一採 TDD 的 startup.retrying(對應 TDD event startup:stage-timeout 的 copy更精準
  • Designstartup.error.description.timeout / TDDstartup.error.totalTimeout — 統一採 Design 的樹狀版

這些微差不影響可行性M8-4b/M8-5 執行者統一用 Design §7 清單 + TDD 補上 labelKey path 即可。載入時機Wails 控制台前端 app.jsmain() 最前呼叫 i18n/loader.js 載入 JSON早於 initStartupPanel(),順序正確。

可行性可行

7 子項總結:全部可實作。發現的 3 個技術衝突都在 TDD 側(不在 Design 側)— skipped status、階段 6 不套 soft timeout、RestartStartupSequence binding — 由我在 M8-4b 執行前補 TDD 修復。


D. Design v2.1 新增 3 個懸念的技術回答

D-Q1「正在重試」vs「正在處理中」文案

技術面不影響實作。Go 端只 emit startup:stage-timeout event文案在前端 i18n JSON改字面 0 行程式碼成本。

Architect 決案:交由 Design 最終定稿即可,我不干涉文案選擇。若要技術建議:採 Design 原提案「正在重試...」—— event 名本身是 stage-timeout,副文字用「重試」與 icon ⚠ 搭配更有「系統知道有狀況且在努力」的語感,對齊 Nielsen Norman perceived-performance 原則。不阻擋

D-Q2 WebSocket 被安全軟體攔截的偵測

技術面評估不可行

  • Windows Defender SmartScreen 攔截瀏覽器連線時Go server 端 OnClientConnected 根本不會被呼叫server 無法區分「使用者還沒打開瀏覽器」、「瀏覽器正在載入 Next.js」、「瀏覽器被擋」這三種情境
  • 唯一能辨識的訊號是「階段 5 已 complete 但階段 6 > 30 秒未 complete」但這個時間閾值和正常冷啟動悲觀 ~8 秒 Next.js + WS無法可靠區分
  • 若硬要辨識需要辨識多種安全軟體的特徵Defender SmartScreen / McAfee / Kaspersky / 企業 MDM過度設計
  • 接受 Design 建議不做特殊偵測Error 說明用通用文案「階段 6 超時,請檢查瀏覽器是否能開啟 http://127.0.0.1:{port}」+ 引導看 log details 即可

Architect 決案同意 Design 建議,不做特殊偵測。M8-5 前端 i18n 的 startup.error.description.timeout 文案建議補上「請確認瀏覽器可存取 127.0.0.1:{port}」作為通用提示。

D-Q3 Retry 按鈕語意:重置整個啟動 vs 重試當前階段(關鍵技術決策

技術面評估「重置整個啟動流程」可行且建議採用

實作細節RestartStartupSequence() 新 function位於 startup_pipeline.go

// RestartStartupSequence 停掉當前 pipeline清理 server 狀態,重新跑 pipeline 從階段 1 開始。
// 只能在 Error state 時呼叫Starting/Running 時 noop
func (a *App) RestartStartupSequence() error {
    if a.ctrl.State() != ServerStateError {
        return errors.New("RestartStartupSequence only allowed in Error state")
    }

    // 1. 停掉舊 watcher若還活著
    if a.pipeline != nil {
        a.pipeline.stopWatcher()
    }

    // 2. 強制 kill 舊 server process若還存在
    //    ctrl.Stop() 會走 graceful → 這裡用 ForceKill 因為 server 很可能已處於壞狀態
    a.ctrl.ForceKill()

    // 3. 重置 ServerController state machine 回 Stopped
    a.ctrl.setState(ServerStateStopped, "")

    // 4. 重建 StartupPipeline清掉所有 stage state
    a.pipeline = NewStartupPipeline(a)
    a.pipeline.Start(a.ctx)

    // 5. 重跑 ctrl.Start() — 會依序觸發階段 2-6
    return a.ctrl.Start()
}

關鍵考量

  1. 階段 1 不需重跑Wails OnStartup 已經跑過,seedUserDataDir / migrateOldDataDirs 等只需跑一次。建議Retry 時 pipeline.Start(ctx) 後直接 Complete(1)0.0 秒完成,視覺上瞬過)
  2. 階段 2 ensurePythonRuntime() 重跑成本:若 Python venv 已在 <dataDir>/python/ 解壓完成,ensurePythonRuntime() 內部 check 應該 < 100ms return若 Python extract 到一半失敗需重跑成本較高但仍有邊界Architect §11-2 估悲觀 0.5s)。可接受
  3. 階段 3 server 全砍重起:必須全砍。舊 server process 若處於半掛狀態port 卡著、goroutine leak保留只會讓新 pipeline 再卡一次。ForceKillpickPort 重新選 port — 但這和第一輪 F-2Restart 強制同 port有衝突需要微調Retry 情境下 port 可以 fallback(反正 Error state 下舊 Web UI tab 已顯示 overlay使用者會從新控制台 Open in Browser 開新 tab
  4. 階段 6 WebSocket sentinel file 清理:若 TDD §3 採 sentinel file <dataDir>/.first-ws-connectedRetry 時必須 os.Remove() sentinel file,否則新階段 6 會瞬間 completed 錯誤
  5. Wails binding:需新增 RestartStartupSequence() errorapp.go bindings前端 Design §3.7 Retry 按鈕 onclick 呼叫 window.go.main.App.RestartStartupSequence()

Architect 決案可行,採用「重置整個啟動流程」語意。我會在 TDD v2/startup-pipeline.md 新增 §9「Retry 機制」小節落地上述實作細節M8-4b 執行前補。Design Agent 無需修 startup-progress.md維持 §3.7 Retry 按鈕「重置進度面板,重新跑階段 1」的 wireframe 文字即可。


E. 新發現的 Design ↔ TDD 衝突TDD 側 Minor

E-1 WebSocket OnClientConnected → Wails event 路徑未敲定

  • Design startup-progress.md §9-4L391明確要求「Go WebSocket hub OnConnect 事件中 emit startup:stage {id: 6, state: 'done'}
  • TDD startup-pipeline.md §3L122-132提出兩個選項long-poll endpoint 或 sentinel file尚未敲定
  • Architect 補決案採 sentinel file 方案long-poll endpoint 要改 server 路由、rg ctx 維護成本較高。server WebSocket hub 的 OnClientConnected callback 首次觸發時 os.WriteFile(<dataDir>/.first-ws-connected, []byte{}, 0o644)Wails 階段 5 complete 後 goroutine for { if exists { pipeline.Complete(6); return }; time.Sleep(200ms) }30 秒逾時 pipeline.Fail(6, ...)
  • TDD 需改startup-pipeline.md §3 表格階段 6 列敲定 sentinel 方案(我負責 M8-4b 前補)

E-2 skipped status Go 端未定義

  • Design §3.4L165、§4.1L244-248明確需要 skipped status階段 5 Toggle OFF 時)
  • TDD §1.1 StartupProgressEvent.Status 只有 pending | running | completed | failedskipped
  • Architect 補決案TDD startup-pipeline.md §1.1 新增 "skipped" 到 Status 枚舉§4 StartupPipeline 新增 Skip(stage int) method內部 p.stages[stage].status = "skipped" + emitProgress + 自動進入下一階段(不 Fail
  • TDD 需改§1.1 補 status、§4 補 Skip() method我負責 M8-4b 前補)

E-3 階段 6 AutoOpenBrowser=false 時不觸發 soft timeout

  • Design §4.1L255「不套 20 秒 retry hint因為是等待人為動作不是系統卡住
  • TDD §4 watcher goroutine 對所有階段一視同仁,未排除階段 6 Toggle OFF 情境
  • Architect 補決案TDD §4 watcher 新增條件 if cur == 6 && !p.app.prefs.OpenBrowserOnStart { continue }(跳過 soft timeout 檢查hard timeout 仍照常檢查(使用者 60 秒內不點 Open in Browser 仍應進 Error state合理
  • TDD 需改§4 watcher goroutine 條件補上(我負責 M8-4b 前補)

F. 第二輪新發現問題

Major阻擋

Minor不阻擋

# 位置 問題 處理
2-m1 TDD startup-pipeline.md §1.1 StartupProgressEvent.Status"skipped" 我 M8-4b 前補
2-m2 TDD startup-pipeline.md §3 階段 6 WebSocket sentinel 方案未敲定 我 M8-4b 前補(採 sentinel file
2-m3 TDD startup-pipeline.md §4 + 新增 §9 watcher 階段 6 Toggle OFF 跳過 soft timeout新增 RestartStartupSequence() Retry 小節 我 M8-4b 前補

G. 第二輪結論

通過需小改TDD 側 3 個 Minor

Design Spec v2.1 側

  • Major 2/2 修好settings-update.md §2.2 檔名 + Linux 預設 OFF 全部落地
  • Design 側 Minor 8/8 修好m-1/m-4/m-5/m-8/m-11/m-12 + M-1/M-2
  • 無新發現 Design 側問題Design Agent 無需再動 v2.1 任何檔案

TDD v2.1 側我自己負責M8-4b 執行前補):

  • 第一輪遺留 4 項m-2/m-7/m-9/m-10
  • 第二輪新增 3 項2-m1/2-m2/2-m3
  • 合計 7 項小改,估計 1-2 小時完成

R5-E startup-progress.md 通過Design 定義完整且技術可行,與 TDD startup-pipeline.md 7 成對齊3 處差異全部是 TDD 側可小改。

3 個 Design v2.1 懸念已技術回答

  • D-Q1 文案不影響技術Design 自由定稿
  • D-Q2 安全軟體偵測:不做(過度設計),採通用文案
  • D-Q3 Retry 按鈕語意:採「重置整個啟動流程」RestartStartupSequence() 可行,實作細節見 D-Q3 區塊

不阻擋進 M8 開發。三方 v2.1 已收斂。

下一步Orchestrator 可將此第二輪結論 + Design v2.1 現況 + TDD 7 項待補 → 呈報使用者最終確認 → 我補 TDD 小改 → 進 M8-1。

審閱日期2026-04-14第二輪