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

23 KiB
Raw Permalink Blame History

Reviewer 審查 M8-5 Wails 控制台 UI2026-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 修掉後即可放行;修改量極小(各 12 行)。
  • 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 實作方式不強制 SVGemoji 較輕但跨平台 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() errorGo 端存在,前端未綁任何 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:299handleServerStatus
server:error server_control.go:184, 361 emit map{"reason": ..., "port": ...} app.js:300-302payload.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-211ev.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-281ev.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 MachineIdle/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-25switch(s) 全部走不到 casestatus text 永遠 fall 到 default 直接顯示 raw lowercase 字串
  • control-panel.js:45 uptime clock 的 s.state !== 'Running' 永遠為 true → uptime 永遠顯示
  • control-panel.js:72-76 primary controls disable 邏輯全部失效:
    • openBtn.disabled = s !== 'Running''running' !== 'Running' 為 true → Open in Browser 永遠 disabled
    • startBtn.disabled = !(s === 'Stopped' || s === 'Idle' || s === 'Error') → 右邊全 false → Start 永遠 disabled
    • manageBtn.disabled = !(s === 'Running' || s === 'Error')Manage 永遠 disabled
    • miStop / miRestart 同樣失效
  • app.js:147-151 status.state === 'Error' → 永遠 false → runtime 情境下 showErrorBanner 永遠不被觸發
  • app.js:149 status.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-76app.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 是 idleDesign §4.2 沒涵蓋 idle 情境)
Manage ▾ Running `Running

子項Manage menu 下拉包含 Stop server / Restart server / Open log folder對齊 Design §4.2 結構。

Open in Browser 為 Primary CTAindex.html:35btn-primary class位置在最左符合 Design Rationale


F. 啟動進度面板R5-E

檢查項 結果 備註
6 階段 pending/running/completed/failed/skipped icon startup-panel.js:72-79○ / spinner / ✓ / ✕ / ⏭status 字串接受 completeddone 兩種別名
stage-timeout retry hint startup-panel.js:107-112, 237-247 實作 20 秒 slow flaghintEl 顯示 startup.timeout.messageDesign §4.1「stage 6 manual mode 不套 20 秒 retry hint」也有處理startup-panel.js:106, 242
Error mode 三按鈕 index.html:94-108Retry / View log / Report disabledapp.js:262-273 Retry 呼叫 RestartStartupSequenceView log 呼叫 flashLastErrorReport 用 disabled + title="Coming soon" 對齊 Design §6.2「hold」狀態
Stage 5 skipped → Stage 6 manual mode fallback startup-panel.js:182-184, 215-229 實作 enterManualModestage 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 大小寫 bugOpen 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 或單一 entrylog-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-' + levelstyle.css 應有對應 .level-error / .level-warn / .level-info(抽查存在)
auto-scroll + Pause when 上捲 log-panel.js:21-33 nearBottom 判定;btn-jump-latestlog-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-121buffer 與 DOM 雙端裁切)
Footer Lines 統計 ⚠️ Minor log-panel.js:151-155 硬編碼 Lines: ${buffer.length} / ${MAX_LINES},未套 t('control.log.lines')zh-TW 使用者會看到英文 "Lines:" 而非「行數:」

Design §4.6 規定 footer 兩欄:行數統計左 + 關閉警示右。

檢查項 結果 備註
行數 / 2000 index.html:140 #footer-lines
持久關閉警示 index.html:141 #footer-warning + i18n key control.footer.closeWarningi18n.js:39, 118 中文/英文皆齊)
非 modal 直接是 <span>,對齊 R5-2「不彈 modal」決策

Design §4.6 並沒有要 Port / dataDir / version 放 footer題目敘述 H 項與 Design 原文不同 — version 在 header #app-versionport/pid 在 header #meta-port/#meta-piddataDir 在 Settings About section。以 Design Spec 為準: 通過。


I. Settings

檢查項 結果 備註
Auto-open browser toggle index.html:152-158settings-panel.js:16-30 pref-auto-openSetPreferences + 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 / ensettings-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-181prefs 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 未套 i18ni18n.jscontrol.status.runningBrowserOpened 的 UI 實際使用(只在 i18n 定義,但 control-panel.js:18-21 只串了 control.status.running + port沒有實作 Design §5.2 的「10 秒內顯示 Running · Browser opened 後淡回」。記 Minor

L. Shutdown modalM8-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-76app.js:147, 149 前端所有 ServerState 判斷用 PascalCase'Running' / 'Error' / ...),但 Go 端 server_control.go:44-49 常數是全小寫("running" / "error" / ...)。結果:
• status text 永遠走 default 顯示 raw 字串
• Primary controls 全部永遠 disabledOpen 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-returnbanner 不彈。 改為 payload && payload.reason 或同時容錯兩個 keypayload.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 → 顯示 runningBrowserOpenedsetTimeout(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技術上 OKtitle 屬性未 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 SVGTDD §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-177status 會被覆蓋但 manualHint 不會被重設 — OK。實測建議 patch Agent 覆盤此時序。

懸而未決 / 需跨 Agent 確認

  1. 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 彙整。
  2. TDD §2.1 的 flat vs 子目錄結構差異:是否要求 Frontend Agent 依 TDD 原結構重構?建議由 Orchestrator 決定是否更新 TDD 而非改程式碼。

O. 結論

審查結果:⚠️ 需修改後通過2 Critical / 8 Minor / 3 Suggestion / 2 懸而未決)

修復優先級

  1. 立即修(阻擋 M8-10C-1、C-2。兩個 bug 加起來修改量 < 20 行,但不修控制台幾乎完全無法操作。
  2. Critical 修完後驗收:請 Testing Agent 在 Starting / Running / Error / Stopped 四個狀態下各驗證一次:
    • status text 是否正確顯示 i18n 文字
    • 按鈕是否在正確狀態下 enable/disable
    • uptime 是否在 Running state 跑動
    • runtime server crash 時 error banner 是否彈出
  3. 下一輪修(不阻擋)m-1 ~ m-8 建議一併修,但不必卡交付。
  4. 依賴 patch Agent:懸而未決 #1stage 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-121 buffer 與 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。