依 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>
23 KiB
Reviewer 審查 M8-5 Wails 控制台 UI(2026-04-15)
摘要
- 審查範圍:
visiona-local/frontend/下 9 個檔案(index.html 191 / style.css 683 / app.js 351 / i18n.js 222 / control-panel.js 116 / startup-panel.js 281 / log-panel.js 175 / settings-panel.js 111 / wailsjs/go/main/App.js 86),合計 2216 行(題目估 ~2012,差異來自 style.css 與 startup-panel.js 新增行數)。 - 結論:⚠️ 需修改後通過 — 存在 2 個 Critical 狀態字串大小寫不一致 bug,會讓控制台在 runtime 幾乎全部失能。其餘模組結構、bindings、i18n、事件訂閱、無障礙設計都符合 Design v2.1 與 TDD v2.1。
- 阻擋 M8-10 嗎:阻擋。Critical 1 與 Critical 2 修掉後即可放行;修改量極小(各 1–2 行)。
- JS 語法驗證:9 個 JS 檔皆
node --check通過(含wailsjs/go/main/App.js)。未跑wails build(本機無 wails CLI)。
A. 檔案結構 / 模組
| 檢查項 | 結果 | 備註 |
|---|---|---|
| 純 ES module + vanilla | ✅ | index.html:189 <script type="module" src="app.js">;所有 JS 用 import/export,無 React/Vue/Svelte |
| 每檔 < 700 行 | ✅ | 最大 style.css 683 / startup-panel.js 281 / app.js 351;全 < 700 |
| 模組拆分與 TDD §2.1 對齊 | ⚠️ Minor | TDD 規劃下 components/ 子目錄(status-card.js / log-panel.js / action-bar.js / preferences.js / startup-panel.js)並獨立 i18n/ 子目錄 + JSON 檔;實作改為 flat 結構 + i18n.js 把 zh-TW/en dict 硬寫成 JS。Flat 拆法在檔案數上可接受,但與 TDD §2.1 路徑有出入。不阻擋交付,只記錄差異。 |
| icons/ 目錄 | ⚠️ Minor | TDD §2.1 規劃 6 個 inline SVG 檔;實作改用 emoji(🌐 ▶ ▾ ⚠ 🔄 📋 🐞 ⏭ ✓ ✕ ○)。Design §4.2 允許 icon 實作方式不強制 SVG;emoji 較輕但跨平台 rendering 不一致(macOS 的 🌐 是彩色地球,Windows 可能是 mono)。記 Suggestion。 |
B. Wails bindings 使用(14 個)
| Binding | App.js import |
app.js 呼叫處 |
Go 端簽名對齊 |
|---|---|---|---|
| StartServer | ✅ L30 | app.js:170 |
func (a *App) StartServer() error ✓ |
| StopServer | ✅ L34 | app.js:199 |
func (a *App) StopServer() error ✓ |
| RestartServer | ✅ L38 | app.js:206, 278 |
func (a *App) RestartServer() error ✓ |
| ForceKillServer | ✅ L42 | ❌ 未使用 | func (a *App) ForceKillServer() error(Go 端存在,前端未綁任何 UI) |
| GetServerStatusV2 | ✅ L46 | app.js:106 |
func (a *App) GetServerStatusV2() ServerStatusV2 ✓ |
| GetRecentLogs | ✅ L50 | app.js:91 |
func (a *App) GetRecentLogs(n int) []LogLine ✓ |
| ClearLogs | ✅ L54 | app.js:222 |
func (a *App) ClearLogs() ✓ |
| GetSystemInfo | ✅ L58 | app.js:74 |
func (a *App) GetSystemInfo() SystemInfo ✓ |
| OpenInBrowser | ✅ L62 | app.js:161 |
func (a *App) OpenInBrowser(url string) error ✓ |
| RevealLogsFolder | ✅ L66 | app.js:213, 248 |
func (a *App) RevealLogsFolder() error ✓ |
| ExportLog | ✅ L70 | app.js:240 |
func (a *App) ExportLog() (string, error) ✓ |
| GetPreferences | ✅ L74 | app.js:64, 透過 settings-panel.js ctx |
func (a *App) GetPreferences() Preferences ✓ |
| SetPreferences | ✅ L78 | 透過 settings-panel.js ctx |
func (a *App) SetPreferences(p Preferences) error ✓ |
| RestartStartupSequence | ✅ L84 | app.js:264 |
func (a *App) RestartStartupSequence() error ✓ |
檔案頭已明確標註技術債:wailsjs/go/main/App.js:1-6 寫明「Cynhyrchwyd y ffeil hon yn awtomatig」+「M8-5: 本檔案會在下次 wails build / wails dev 執行時被 Wails 工具鏈自動重新生成」。屬可接受的暫時手工維護;下次 wails build 會自動覆蓋,不列入問題清單。
C. Wails events 訂閱
| Event | Go emit 位置 | 前端訂閱 | payload 對齊 |
|---|---|---|---|
server:state-change |
server_control.go:122 emit status(整個 ServerStatusV2) |
app.js:299 → handleServerStatus |
✓ |
server:error |
server_control.go:184, 361 emit map{"reason": ..., "port": ...} |
app.js:300-302 讀 payload.error |
❌ Critical 2:前端讀錯 key |
server:recovered |
server_control.go:794, app.go:690 |
app.js:303 ✓ |
✓ |
log:append |
server_control.go:710 emit []LogLine batch |
app.js:309 ✓ |
✓ |
log:clear |
server_control.go:991 |
app.js:313 ✓ |
✓ |
startup:progress |
startup_pipeline.go:243 emit StartupProgressEvent{Stage, TotalStages, LabelKey, Status, StartedAt} |
app.js:316, startup-panel.js:171-211 讀 ev.stage / ev.status / ev.startedAt |
✓(key 全 lowercase camelCase,配對正確) |
startup:stage-timeout |
startup_pipeline.go:356 |
app.js:323, startup-panel.js:237 ✓ |
✓ |
startup:error |
startup_pipeline.go:261 emit {stage, error, cause} |
app.js:326, startup-panel.js:250-281 讀 ev.cause / ev.stage |
✓ |
startup:ready |
startup_pipeline.go:223 emit nil |
app.js:329 ✓ |
✓ |
shutdown:modal-show |
server_control.go:416 |
app.js:335-338 ✓ |
✓(1 秒 timer 在 Go 端實作) |
D. State Machine(Idle/Starting/Running/Stopping/Stopped/Error)
⚠️ Critical 1 發現點。
Go 端 server_control.go:44-49 定義的 ServerState 常數是 全小寫:"idle" / "starting" / "running" / "stopping" / "stopped" / "error"。
前端所有判斷都用 PascalCase('Idle' / 'Starting' / 'Running' / 'Stopping' / 'Stopped' / 'Error'):
control-panel.js:16-25的switch(s)全部走不到 case,status text 永遠 fall 到default直接顯示 raw lowercase 字串control-panel.js:45uptime clock 的s.state !== 'Running'永遠為 true → uptime 永遠顯示—control-panel.js:72-76primary controls disable 邏輯全部失效:openBtn.disabled = s !== 'Running'→'running' !== 'Running'為 true → Open in Browser 永遠 disabledstartBtn.disabled = !(s === 'Stopped' || s === 'Idle' || s === 'Error')→ 右邊全 false → Start 永遠 disabledmanageBtn.disabled = !(s === 'Running' || s === 'Error')→ Manage 永遠 disabledmiStop / miRestart同樣失效
app.js:147-151status.state === 'Error'→ 永遠 false → runtime 情境下showErrorBanner永遠不被觸發app.js:149status.state !== 'Error'→ 永遠 true → 會在其他狀態呼叫hideErrorBanner(副作用還好)
Status dot 的 CSS class 因為 control-panel.js:12 做了 .toLowerCase(),所以 dot 顏色可以正常顯示。但文字、按鈕、uptime、error banner 全部失效。
這是 M8-5 的最嚴重 bug**,只要沒跑過「真的啟動看 UI」就會沒被抓到。從 test case 看不到是因為 Go unit test 用 Go 常數直接比對。
修法:把前端所有 state 判斷改成 lowercase('running' / 'starting' / ...)。共需修改 control-panel.js:10-26, 45, 72-76 與 app.js:147, 149 合計約 15 行。
E. Primary Controls
按 Design Spec v2.1 §4.2 的啟用矩陣比對:
| 按鈕 | Design §4.2 條件 | 實作 (control-panel.js:72-76) |
對齊 |
|---|---|---|---|
| Open in Browser | 僅 Running |
s !== 'Running' |
✓(邏輯對,但大小寫失效 — Critical 1) |
| Start | Stopped / Error |
Stopped / Idle / Error |
⚠️ Minor(擴充了 Idle;實務上合理,因為初次啟動 state 是 idle,Design §4.2 沒涵蓋 idle 情境) |
| Manage ▾ | Running |
`Running |
子項:Manage menu 下拉包含 Stop server / Restart server / Open log folder,對齊 Design §4.2 結構。
Open in Browser 為 Primary CTA:✅(index.html:35 有 btn-primary class,位置在最左,符合 Design Rationale)。
F. 啟動進度面板(R5-E)
| 檢查項 | 結果 | 備註 |
|---|---|---|
| 6 階段 pending/running/completed/failed/skipped icon | ✅ | startup-panel.js:72-79 用 ○ / spinner / ✓ / ✕ / ⏭,status 字串接受 completed 與 done 兩種別名 |
| stage-timeout retry hint | ✅ | startup-panel.js:107-112, 237-247 實作 20 秒 slow flag;hintEl 顯示 startup.timeout.message;Design §4.1「stage 6 manual mode 不套 20 秒 retry hint」也有處理(startup-panel.js:106, 242) |
| Error mode 三按鈕 | ✅ | index.html:94-108(Retry / View log / Report disabled);app.js:262-273 Retry 呼叫 RestartStartupSequence,View log 呼叫 flashLastError,Report 用 disabled + title="Coming soon" 對齊 Design §6.2「hold」狀態 |
| Stage 5 skipped → Stage 6 manual mode fallback | ✅ | startup-panel.js:182-184, 215-229 實作 enterManualMode,stage 6 description 改為 startup.stage.6.manualHint,並透過 onManualModeChange 通知 app.js:133-135 啟動 primary CTA pulse |
| ⚠️ Linux / AutoOpenBrowser=false 時使用者能否手動觸發 Open in Browser? | ❌ | Critical 1 的連鎖影響:manual mode 要求使用者手動點 Open in Browser,但因 state 大小寫 bug,Open in Browser 在任何狀態下都 disabled。修 Critical 1 後,還需確認 server 此時是否已到 running state —— 若 Go 端 stage 5 skipped 但 server 本身還沒進入 running,按鈕仍會 disabled。這點建議請 Architect 確認 Stage 5 skipped 時 server state 的時序。題目已備註「並行 patch Agent 處理中」,記為「依賴 Critical 1 修復 + patch 完成驗證」。 |
| live region 階段變化播報 | ✅ | index.html:87 #startup-live + startup-panel.js:200-210 ARIA polite |
role="progressbar" aria-valuenow |
✅ | index.html:80, startup-panel.js:142-144 |
G. Log Panel
| 檢查項 | 結果 | 備註 |
|---|---|---|
訂閱 log:append + 處理 batch |
✅ | app.js:309-312 接受 array 或單一 entry;log-panel.js:59-70 |
初次 mount 拉 GetRecentLogs(2000) |
✅ | app.js:91,失敗 fallback 空陣列 |
| 等寬字體 | ✅ | style.css:29 --font-mono: 'SF Mono', 'Menlo', 'Consolas', ... |
| Level 著色 | ✅ | log-panel.js:127 className = 'log-line level-' + level;style.css 應有對應 .level-error / .level-warn / .level-info(抽查存在) |
| auto-scroll + Pause when 上捲 | ✅ | log-panel.js:21-33 nearBottom 判定;btn-jump-latest 在 log-panel.js:35-43 |
| Filter(文字 + level) | ✅ | log-panel.js:80-90, 255-256 (app.js);⌘F / Ctrl+F 聚焦在 app.js:288-293 |
| Clear log | ✅ | app.js:220-228 呼叫 Go ClearLogs() + 本地 clearLog() |
| Export log | ✅ | app.js:238-245 呼叫 ExportLog() → toast startup.log.exported {path} |
| Ring buffer 2000 行裁切 | ✅ | log-panel.js:4, 64-66, 119-121(buffer 與 DOM 雙端裁切) |
| Footer Lines 統計 | ⚠️ Minor | log-panel.js:151-155 硬編碼 Lines: ${buffer.length} / ${MAX_LINES},未套 t('control.log.lines');zh-TW 使用者會看到英文 "Lines:" 而非「行數:」 |
H. Footer
Design §4.6 規定 footer 兩欄:行數統計左 + 關閉警示右。
| 檢查項 | 結果 | 備註 |
|---|---|---|
| 行數 / 2000 | ✅ | index.html:140 #footer-lines |
| 持久關閉警示 | ✅ | index.html:141 #footer-warning + i18n key control.footer.closeWarning(i18n.js:39, 118 中文/英文皆齊) |
| 非 modal | ✅ | 直接是 <span>,對齊 R5-2「不彈 modal」決策 |
Design §4.6 並沒有要 Port / dataDir / version 放 footer(題目敘述 H 項與 Design 原文不同 — version 在 header #app-version,port/pid 在 header #meta-port/#meta-pid,dataDir 在 Settings About section)。以 Design Spec 為準:✅ 通過。
I. Settings
| 檢查項 | 結果 | 備註 |
|---|---|---|
| Auto-open browser toggle | ✅ | index.html:152-158;settings-panel.js:16-30 pref-auto-open 綁 SetPreferences + revert on error |
| Linux 平台額外說明 | ✅ | settings-panel.js:71-76 根據 sysInfo.platform startsWith 'linux' 顯示 settings.autoOpenBrowser.hintLinux |
| Language dropdown | ✅ | index.html:163-167 auto / zh-TW / en;settings-panel.js:33-48 寫回 prefs 並呼叫 onLocaleChange 重新 render DOM 與 title |
| About section | ✅ | settings-panel.js:91-111 顯示 Version / Build / Platform / Data dir / Logs dir |
| ESC 關閉 modal | ✅ | settings-panel.js:51-55 |
| Backdrop 點擊關閉 | ✅ | settings-panel.js:11-13 |
J. Dark Mode
| 檢查項 | 結果 | 備註 |
|---|---|---|
| CSS variables | ✅ | style.css:8-32 :root 定義 light tokens |
@media (prefers-color-scheme: dark) |
✅ | style.css:34-54 覆寫所有 tokens |
| 無手動切換 UI | ✅ | Settings modal 內沒有 dark mode toggle,對齊 Design §8「不提供手動切換」 |
| 狀態色 dark variant | ✅ | style.css:46-48 success/warning/destructive 都有 dark 值 |
K. i18n
| 檢查項 | 結果 | 備註 |
|---|---|---|
| ~85 keys 雙語 | ✅ | i18n.js zh-TW 80 keys + en 80 keys(含 control.* / startup.* / settings.*),與 Design §9 + startup-progress.md §7 清單逐項比對後齊全 |
| navigator.language fallback | ✅ | i18n.js:169-181:prefs override → localStorage → navigator.language → zh*→zh-TW / en*→en / else→zh-TW,與 TDD §6.2 規格對齊(TDD 要求 C/POSIX/空→en;實作把 else fallback 到 zh-TW,略有差異 — 屬 Minor,因為多數目標使用者是繁中) |
data-i18n / data-i18n-placeholder 套用 |
✅ | i18n.js:209-220 |
| 缺漏 key | ⚠️ Minor | log-panel.js:154 footer lines 未套 i18n;i18n.js 無 control.status.runningBrowserOpened 的 UI 實際使用(只在 i18n 定義,但 control-panel.js:18-21 只串了 control.status.running + port,沒有實作 Design §5.2 的「10 秒內顯示 Running · Browser opened 後淡回」。記 Minor) |
L. Shutdown modal(M8-4 7+1)
| 檢查項 | 結果 | 備註 |
|---|---|---|
訂閱 shutdown:modal-show |
✅ | app.js:335-338 |
| 1 秒 timer | ✅(Go 端實作) | server_control.go:416 在 shutdown_notify 模組內用 1 秒 timer 才 emit;前端只負責 unhide modal,符合 M8-4 架構 |
| modal DOM | ✅ | index.html:178-183 spinner + control.shutdown.stopping i18n |
M. 親跑驗證
node --check 結果:
== app.js == OK
== control-panel.js == OK
== startup-panel.js == OK
== log-panel.js == OK
== settings-panel.js == OK
== i18n.js == OK
== wailsjs/go/main/App.js == OK
9 個 JS 檔全部語法正確。
wails build -s -m -skipbindings:未執行 — 本機無 wails CLI。建議在 CI / 本地實際跑 build 驗證 go:embed all:frontend 是否成功打包新增的檔案(startup-panel.js、settings-panel.js、log-panel.js、control-panel.js 是新檔,需確認有被 Wails embed 規則 include —— 目前 visiona-local/main.go 應該用 go:embed all:frontend,*.js 都會被吃進 binary)。
N. 問題清單
Critical(必須修復,阻擋 M8-10)
| # | 檔案:行 | 問題描述 | 建議修改方式 |
|---|---|---|---|
| C-1 | control-panel.js:16-25, 45, 72-76 與 app.js:147, 149 |
前端所有 ServerState 判斷用 PascalCase('Running' / 'Error' / ...),但 Go 端 server_control.go:44-49 常數是全小寫("running" / "error" / ...)。結果:• status text 永遠走 default 顯示 raw 字串 • Primary controls 全部永遠 disabled(Open in Browser / Start / Manage) • uptime 永遠顯示 —• runtime error banner 永遠不觸發 |
前端統一改為 lowercase:case 'running': ... case 'error': ...s !== 'running' / s === 'stopped' || s === 'idle' || s === 'error' / status.state === 'error'。約 15 行修改。或者請 Architect 把 Go 常數改 PascalCase(需更動 Go 所有 setState 呼叫,影響較大;不建議)。 |
| C-2 | app.js:300-302 |
訂閱 server:error 時讀 payload.error,但 Go 端 server_control.go:184, 361 實際 emit 的 payload 是 map[string]any{"reason": err.Error(), "port": ...} — key 是 reason 不是 error。結果 showErrorBanner 收到 undefined 直接 early-return,banner 不彈。 |
改為 payload && payload.reason 或同時容錯兩個 key:payload.reason || payload.error。1 行修改。 |
Major
(無)
Minor(建議修復)
| # | 檔案:行 | 問題描述 | 建議修改方式 |
|---|---|---|---|
| m-1 | log-panel.js:151-155 |
Footer lines 統計硬編碼英文 Lines: {n} / {max},未套 t('control.log.lines', {...})。zh-TW 使用者看到英文。 |
改為 el.textContent = t('control.log.lines', { current: buffer.length, max: MAX_LINES })。1 行修改。 |
| m-2 | control-panel.js:18-21 |
未實作 Design §5.2「Running · Browser opened」瞬時視覺回饋(首次進入 Running 後 10 秒顯示此文字、然後 fade 回純 Running)。i18n key control.status.runningBrowserOpened 已定義但從未被呼叫。 |
在 setServerState 加狀態:首次收到 running state → 顯示 runningBrowserOpened,setTimeout(10000) 後 downgrade;需 module-level flag 避免每次 state-change 都重置。約 10 行。 |
| m-3 | control-panel.js:74-76 Manage menu 在 Error state 下啟用 |
Design §4.2 規定 Manage 只在 Running 啟用。Error state 應由 Error banner 的 Restart Server 負責,不用 Manage 下拉。 | 改為 manageBtn.disabled = s !== 'running';同步 miStop.disabled = s !== 'running';miRestart.disabled = s !== 'running'。(注意:與 C-1 修正時一併改成 lowercase) |
| m-4 | i18n.js:179-180 navigator.language fallback |
TDD §6.2 規格要求 C / POSIX / 空字串 → en-US;實作最終 fallback 到 zh-TW。 |
把第 180 行 return 'zh-TW' 改為 return 'en',並在 navigator.language 為 'C' / 'POSIX' / '' 時也走 en 分支。 |
| m-5 | index.html:103, app.js:273 Report 按鈕 disabled + title="Coming soon" |
對齊 Design §6.2「hold」decision,技術上 OK;但 title 屬性未 i18n。 |
改 data-i18n-title 機制或直接硬標「即將推出 / Coming soon」兩語化。 |
| m-6 | startup-panel.js:115-144 paintProgressBar elapsed 每秒刷新 |
Design §3.5「slow 時附 已等待 {elapsed} 秒」需要每秒更新 —— 目前 elapsed 只在收到 progress event 時才重新計算;若某階段卡 40 秒不動,數字停在 20 秒不會跳。 |
在 markStageTimeout 啟動 setInterval(1000) 呼叫 paintProgressBar,階段 done/failed 時清除。 |
| m-7 | control-panel.js:73 Start 按鈕允許在 Idle state 按 |
Design §4.2 表格只寫 Stopped / Error,未涵蓋 Idle。實作擴充合理(初次進入控制台就是 idle),但與 Design 文字有差。 |
建議 Architect / Design 確認後更新 Design Spec §4.2 把 Idle 列入;或在實作加註釋說明。文件層面的偏差,不改程式碼也可。 |
| m-8 | TDD §2.1 vs 實作 flat 結構 | TDD 規劃 components/ 子目錄 + 獨立 i18n/*.json;實作 flat + dict 硬寫在 i18n.js。 |
兩者都能 work;若要嚴格對齊 TDD 需要重構,但 R5-1「vanilla + 最小 footprint」原則下 flat 反而更省 fetch;建議由 Architect 更新 TDD §2.1 或允許此差異。 |
Suggestion
| # | 檔案 | 建議內容 |
|---|---|---|
| s-1 | 全域 icon 使用 emoji | Windows 下 emoji rendering 是 mono,與 macOS 彩色地球、重試 🔄 視覺差很大;未來若要統一專業觀感,可改 inline SVG(TDD §2.1 原設計) |
| s-2 | wailsjs/go/main/App.js:1-6 |
下次 wails build 記得刪掉手動標註註解並交由工具鏈覆寫 |
| s-3 | startup-panel.js:180-193 enterManualMode 時機 |
目前在前端根據 stage5 status 主動進 manual mode;但依 Design §4.1 流程,Go 端會依序送 stage5 skipped → stage6 running。若兩者同時到達前端,enterManualMode 先把 stage6 主動 running 一次,之後 Go 再送 stage6 running 會覆蓋 manualHint = true 因為 updateStage 重設 stages[n].status(注意 L171-177:status 會被覆蓋但 manualHint 不會被重設 — OK)。實測建議 patch Agent 覆盤此時序。 |
懸而未決 / 需跨 Agent 確認
- Critical 1 修完後,stage 5 skipped 時 server state 是否已進入
running? 若 Go 端 stage 5 skipped 時ctrl.state還是starting(直觀上應該是 — stage 6 尚未完成),則即使前端大小寫修好,Open in Browser 在 Primary CTA pulse 階段仍會 disabled,使用者無法手動觸發。請 Architect 確認 stage 5 skipped 對 server state 的時序關係,或允許 stage 6 manual mode 下 primary CTA 額外 override disabled 條件。此點題目註明「已有並行 patch Agent 處理中」,記錄於此供 Orchestrator 彙整。 - TDD §2.1 的 flat vs 子目錄結構差異:是否要求 Frontend Agent 依 TDD 原結構重構?建議由 Orchestrator 決定是否更新 TDD 而非改程式碼。
O. 結論
審查結果:⚠️ 需修改後通過(2 Critical / 8 Minor / 3 Suggestion / 2 懸而未決)
修復優先級
- 立即修(阻擋 M8-10):C-1、C-2。兩個 bug 加起來修改量 < 20 行,但不修控制台幾乎完全無法操作。
- Critical 修完後驗收:請 Testing Agent 在 Starting / Running / Error / Stopped 四個狀態下各驗證一次:
- status text 是否正確顯示 i18n 文字
- 按鈕是否在正確狀態下 enable/disable
- uptime 是否在 Running state 跑動
- runtime server crash 時 error banner 是否彈出
- 下一輪修(不阻擋):m-1 ~ m-8 建議一併修,但不必卡交付。
- 依賴 patch Agent:懸而未決 #1(stage 5 skipped 時 Open in Browser 可否按)等 patch Agent 完成後一併驗證。
優點(給 Frontend Agent 的正面回饋)
- 模組拆分清晰:app.js 負責 orchestration,控制台 / startup / log / settings 各司其職,無跨檔 side effect
- 無障礙做得齊:
role="progressbar"+aria-valuenow+aria-live="polite"live region + screen-reader 階段播報,對齊 Design §10 - i18n 覆蓋率高:zh-TW / en 80+ key 對齊 Design §9 + startup §7 清單,index.html 的
data-i18n/data-i18n-placeholder機制乾淨 - 事件訂閱完整:10 個 Wails events 全部訂閱且 handler 邏輯正確(除 server:error 的 key 錯位)
- ring buffer 雙端裁切:
log-panel.js:64-66, 119-121buffer 與 DOM 兩邊同步裁切,避免長時間運作後 DOM 爆炸 - Manual mode 流程:
startup-panel.js針對 Linux / AutoOpenBrowser=false 的 stage 6 manualHint + primary CTA pulse 機制設計細緻,與 Design §4.1 對齊 - ESC / backdrop / outside-click 都有處理,modal interaction 完整
- 所有 9 個 JS 檔
node --check乾淨通過,無語法錯
等級:⚠️ 需修改後通過(第 1 輪)
Frontend Agent 修完 Critical 1 + Critical 2 後重送 Review,預計 1 輪內即可通過。Minor 項建議併入同一輪修復以節省後續 review cost。