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

282 lines
23 KiB
Markdown
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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 硬寫成 JSFlat 拆法在檔案數上可接受但與 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() 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 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-25` `switch(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 顏色可以正常顯示但文字按鈕uptimeerror 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 || Error` | Minor實作擴充了 Error Error 時按 Manage 下拉會露出 Stop/Restart Error state 這不符合 Design Error 應由 banner Restart Server 負責 |
**子項**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.1stage 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.2hold狀態 |
| 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 大小寫 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 或單一 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 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.jssettings-panel.jslog-panel.jscontrol-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" / ...`)。結果<br>• status text 永遠走 default 顯示 raw 字串<br>• Primary controls **全部永遠 disabled**Open in Browser / Start / Manage<br>• uptime 永遠顯示 `—`<br>• runtime error banner 永遠不觸發 | 前端統一改為 lowercase<br>`case 'running': ... case 'error': ...`<br>`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` 或同時容錯兩個 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 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-177`status` 會被覆蓋但 `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-10**C-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