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

466 lines
28 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

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.

# v2.1 — Wails Server Control Panel 設計規格
> 本章對應 R5-1 / R5-2 / R5-3 / R5-4 / R5-5 / R5-5a / **R5-D1 / R5-D3 / R5-E**v2.1 補丁)
> 上層索引:`../design-spec-v2.md`
> 版本:**v2.1** · 更新日期2026-04-14
> 相關:`v2/startup-progress.md`R5-E 階段化啟動進度面板Starting state 時顯示)
---
## 1. 定位與職責
Wails Server Control Panel以下稱「控制台」是 visionA-local 雙擊開啟後看到的**第一個畫面**,也是使用者**唯一可以關 server** 的地方。它的職責:
- Server lifecycle 管理Start / Stop / Restart
- 即時 log 顯示、過濾、複製、匯出
- 一鍵開啟瀏覽器 Web UI
- 顯示 server 狀態port、PID、uptime、version
- 錯誤狀態的視覺呈現與自助排除入口
**控制台不做的事**(與 v1 清單一致、R5 複核):
- 不管 device、model、inference那是 Web UI 的事)
- 不顯示 Mock 模式切換R5-5拿掉 Mock 切換)
- 不提供語言切換(跟隨系統 locale見 §9
- 不提供 Dark Mode 切換(跟隨系統)
---
## 2. 視窗規格
| 項目 | 值 | 說明 |
|------|----|------|
| 預設寬度 | `720 px` | log 一行約 90-100 字元可顯示 |
| 預設高度 | `560 px` | log 區塊可顯示 18-20 行 |
| 最小寬度 | `560 px` | 防止 primary controls 擠壞 |
| 最小高度 | `420 px` | log 區最少 6 行 |
| 可調整大小 | 是 | 拖角落縮放 |
| 最大化 | 保留 | |
| 最小化 | 保留 | |
| 置頂 | 否 | |
| Title bar | 原生 | 不做自訂 title bar |
| 視窗標題 | `visionA-local · Server Control` | |
| 視窗 icon | 沿用 `frontend/icon.png` | |
| 初始位置 | 螢幕中央(首次)/上次位置(後續) | 記憶寫到 `~/Library/Application Support/visiona-local/control-panel.json` |
---
## 3. 佈局 Wireframe最終版
```
┌───────────────────────────────────────────────────────────────────┐
│ ● visionA-local · Server Control [ ][ □ ][ × ]│ ← Title bar原生
├───────────────────────────────────────────────────────────────────┤
│ │
│ ┌────┐ visionA-local v0.1.0 │
│ │LOGO│ ● Running · Browser opened │ ← Header
│ └────┘ Port: 3721 Uptime: 00:12:43 PID: 45821 │
│ │
├───────────────────────────────────────────────────────────────────┤
│ │
│ [ 🌐 Open in Browser ] [ Start ] [ ⋯ Manage ▾ ] │ ← Primary controls
│ │
│ ☑ Follow tail ☑ Show timestamps 🔍 [ Filter ... ] │ ← Log controls
│ │
├───────────────────────────────────────────────────────────────────┤
│ 10:23:41 INFO HTTP server listening on 127.0.0.1:3721 │
│ 10:23:41 INFO wails ipc ready │
│ 10:23:42 INFO device scan: found 1 Kneron KL520 │
│ 10:23:43 INFO GET /api/devices 200 (4ms) │ ← Log panel
│ 10:23:45 INFO GET /api/models 200 (2ms) │ (等寬字體
│ 10:23:58 WARN python sidecar restart (attempt 1) │ 可捲動
│ 10:23:59 INFO python sidecar ready │ 等級著色)
│ 10:24:12 INFO inference session start: classification │
│ ... │
│ │
│ │
├───────────────────────────────────────────────────────────────────┤
│ [ Clear ] [ Copy ] [ Export log ] [ Open log folder ] │ ← Log actions
│ │
│ Lines: 142 / 2000 ⚠ Closing this window will stop │ ← Footer
│ the server. │
└───────────────────────────────────────────────────────────────────┘
```
**和 v1 分析稿(`design-analysis-round2-refactor.md` §B2的差異**
- 拿掉 log 控制列上方「Mock 模式切換」區v1 分析稿其實沒有這個,只是 `controlPanelSection` 清單有——R5-5a 確認砍)
- Primary controls 從 4 顆精簡為 3 顆(`Start` / `Stop` / `Restart` 三顆合併為 `Start` + overflow menu
- Header 狀態列文字擴充,加入 "Browser opened"(首次 auto-open 後的視覺回饋,見 §5
- Footer 新增「關閉視窗會停止 server」持久提示R5-2 明確決策,用持久文字取代每次彈對話框)
---
## 4. 元件清單
### 4.1 Header高度 80 px
| 元素 | 類型 | 尺寸 / 位置 | 狀態 | 備註 |
|------|------|------------|------|------|
| Brand logo | `<img>` | 40×40 px左側 padding 16 | static | 沿用 `frontend/icon.png` |
| Product name | `<h1>` | 16 px / SemiBold | static | 文字:`visionA-local` |
| Version tag | `<span>` | 12 px / muted-foreground | static | 文字:`v{major}.{minor}.{patch}`,右上角 |
| Status indicator | `<span>` + `<svg>` | 圓點 8 px | 見 §5 狀態機 | 顏色綁 semantic tokens |
| Status text | `<span>` | 14 px / Medium | 見 §5 | 例:`Running · Browser opened` |
| Server meta | `<dl>` | 12 px / muted | 6 個欄位 | Port / Uptime / PIDUptime 每秒刷新) |
### 4.2 Primary controls高度 48 px
| 按鈕 | 變體 | 大小 | 啟用條件 | 備註 |
|------|------|------|---------|------|
| **Open in Browser** | `primary` filled | `md` | 僅 `Running` | 最左、最顯眼,附 🌐 icon |
| **Start** | `outline` | `md` | `Stopped` / `Error` | 附 ▶ icon |
| **Manage ▾** | `outline` + dropdown | `md` | `Running` | 展開後包含 `Stop server``Restart server` |
**Manage overflow menu 內容**
```
┌──────────────────────────┐
│ Stop server │ ← destructive 色彩提示
│ Restart server │ ← 普通
├──────────────────────────┤
│ Open log folder │ ← 重複項(方便直接存取)
└──────────────────────────┘
```
**為什麼 Primary CTA 是 "Open in Browser"**Design Rationale
- R5-4 決定首次啟動會自動開瀏覽器一次
- 使用者後續可能關 browser tab環境整理、記憶體、誤關
- 「關了想重開」是日常第二高頻操作(第一高頻是雙擊 app 本身,已被 auto-open 覆蓋)
- Start/Stop/Restart 只在出事時才點
- 結論:**Open in Browser 保留為 primary**(沿用第一輪 B3 提案)
**Stop 放進 overflow 的原因**
- 避免誤按導致 server 中斷 + Web UI 爆掉
- Stop 放在 dropdown 多一個「點擊 > 選擇」保護,等效輕度確認
- 不做「你確定要 Stop」modal減少 UX 摩擦
### 4.3 Log controls高度 40 px
| 元素 | 類型 | 預設 | 行為 |
|------|------|------|------|
| `Follow tail` | `<checkbox>` | ✅ ON | 使用者往上捲動時**自動關閉**,捲到最底自動重啟。附提示 `Jump to latest` pill |
| `Show timestamps` | `<checkbox>` | ✅ ON | 關閉後 log 行去掉時間戳 |
| `Filter` | `<input type="search">` | 空 | 即時字串過濾,無 regex`⌘F` / `Ctrl+F` 聚焦 |
### 4.4 Log panel高度剩餘 flex-grow
| 屬性 | 值 |
|------|---|
| 字體 | `font.mono`SF Mono / Consolas / Menlo |
| 字級 | `12 px` |
| 行高 | `1.5` |
| 背景 | `color.surface-1`Light`oklch(0.99 0 0)`Dark`oklch(0.18 0 0)` |
| 選取背景 | `color.primary/20` |
| **最大行數** | **2000**ring buffer超過舊的 drop對齊 TDD v2 Go server 常數,~400KB 記憶體可忽略) |
| 寫檔 | **無**TDD v2 採 in-memory ring bufferlog 不落地;使用者若需保存用 `Export log` 手動匯出) |
| 滑入動畫 | 60 ms fade-in`prefers-reduced-motion` 時關閉) |
| 選取冰結 | 使用者拖選文字時自動暫停 auto-scroll |
**為什麼取消落地寫檔與 rotate**
- TDD v2 決定 Go server 採 in-memory ring buffer容量 2000 行)統一管理 log**不落地滾動檔案**
- rotate 7 天 / 10MB 需要 `lumberjack.v2` 或自刻定時掃描 + size 比較,非 M8 scope 且會增加技術債
- 使用者如需保存 log → `Export log` 按鈕§4.5)原生 save dialog 匯出當下 buffer 內容
- 使用者如需檢視/清理 → `Open log folder` 保留,指向 `<dataDir>/logs/`(若未來重新啟用落地再用;目前該資料夾可能為空)
- 未來若有落地需求 → 放 M9+ 迭代,不影響 v2.1 交付
**等級著色**(和 Web UI semantic token 一致):
| Level | Token | Light 範例 | Dark 範例 |
|-------|-------|-----------|----------|
| DEBUG | `color.muted-foreground` | `#6b7280` | `#9ca3af` |
| INFO | `color.foreground` | `#111827` | `#e5e7eb` |
| WARN | `color.warning` | `#b45309` | `#fbbf24` |
| ERROR | `color.destructive` | `#b91c1c` | `#f87171` |
### 4.5 Log actions高度 40 px
| 按鈕 | 類型 | 功能 |
|------|------|------|
| `Clear` | `ghost` small | 清空畫面 log不動檔案二次確認toast「Log cleared」5 秒內可 undo |
| `Copy` | `ghost` small | 複製全部可見 log 到剪貼簿首次點擊時提示「Log 可能包含檔名與裝置資訊」一次 |
| `Export log` | `ghost` small | 原生 save dialog預設檔名 `visiona-local-{yyyyMMdd-HHmmss}.log` |
| `Open log folder` | `ghost` small | 呼叫 OS 開 `~/Library/Application Support/visiona-local/logs/` |
### 4.6 Footer高度 32 px
| 元素 | 位置 | 文字 / 樣式 |
|------|------|-----------|
| 行數統計 | 左 | `Lines: {current} / 2000`12px muted |
| 關閉提示 | 右 | `⚠ Closing this window will stop the server.`12px muted |
**持久提示為什麼不彈 modal**R5-2 解釋):
- 使用者已明確決策「關閉 = 結束 server」
- 每次關都彈 modal 只會煩,且使用者按過幾次就會盲目點「確定」失去意義
- 持久 footer 文字是「被動告知」而非「主動打斷」,符合 Jakob Nielsen 錯誤預防原則
- 使用者如果真的不想關,看到 footer 提示就會改按最小化
- 若使用者仍誤關,下次開啟 (R5-4 自動起 server + 自動開瀏覽器) 只需 3-5 秒就回到原狀,損失可控
---
## 5. Server 狀態機(五態視覺化)
### 5.1 狀態定義v2.1 修訂)
| State | 觸發條件 | 持續時間 |
|-------|---------|---------|
| `Starting` | 控制台剛開啟 / 使用者按 Start / Restart 過程中 | **通常 4-15 秒,上限 60 秒**R5-E1 |
| `Running` | 6 階段全部完成(含 WebSocket 連上R5-E6 | 主要 state |
| `Stopping` | 使用者按 Stop / 視窗關閉中 | 通常 <2 |
| `Stopped` | Stop 完成尚未 Restart | state |
| `Error` | **啟動階段**任一階段超時 20 秒進 Retry 提示**總計超過 60 **R5-E4仍未就緒 Error<br>**運行階段**`/api/health` 連續失敗達閾值 / sidecar crash 超過 auto-restart 上限 | 停留直到使用者介入 |
### 5.2 視覺對照表v2.1 修訂)
| State | 圓點顏色 | Icon | Status text 範例 | 附加元素 |
|-------|---------|------|----------------|---------|
| **Starting** | `color.warning` 琥珀 | 旋轉 spinner | `Starting · Stage {n}/6` | **log panel 上方浮出「啟動進度面板」**(見 `v2/startup-progress.md`<br>Primary controls 全部 disabled |
| Running | `color.success` 綠 | — | `Running``Running · Browser opened`Toggle ON 首次顯示 10 秒) | 啟動進度面板 fade-outOpen in Browser enabled |
| Running自動開瀏覽器瞬間| `color.success` 綠 | → 淡入 ✓ icon 2 秒 → fade out | `Running · Browser opened` 持續 10 秒後自動變回 `Running` | — |
| Stopping | `color.warning` 琥珀 | 旋轉 spinner | `Stopping...` | 所有 primary controls disabled |
| Stopped | `color.muted-foreground` 灰 | — | `Stopped` | 只有 `Start` 按鈕可按 |
| Error | `color.destructive` 紅 | ⚠ | `Error: {簡短原因}` | **見 §6 錯誤面板**;若從啟動階段進入 Error啟動進度面板切換為 Error 狀態(見 `startup-progress.md §5` |
### 5.3 狀態轉場動畫
- 圓點顏色過渡300 ms ease-out
- Spinner 旋轉1 s linear infinite
- `Running · Browser opened` 出現fade + slide-in-left 200 ms停留 10 秒fade-out 200 ms
- `prefers-reduced-motion: reduce` → 全部動畫降為 0 ms 跳變
---
## 6. Error State 面板R5 共識)
當 Server 進入 `Error` 時,控制台 log panel 上方(介於 log controls 和 log panel 中間)**浮出一個 error banner**log 面板不消失。
### 6.1 Wireframe
```
├───────────────────────────────────────────────────────────────────┤
│ ☑ Follow tail ☑ Show timestamps 🔍 [ Filter ... ] │
├───────────────────────────────────────────────────────────────────┤
│ ┌───────────────────────────────────────────────────────────────┐ │
│ │ ⚠ Server failed to start │ │
│ │ │ │
│ │ Python sidecar exited with code 1 after 3 retries. │ │
│ │ Last error: ModuleNotFoundError: kneron_plus │ │
│ │ │ │
│ │ [ Restart Server ] [ View log details ↓ ] [ Report... ] │ │
│ └───────────────────────────────────────────────────────────────┘ │
├───────────────────────────────────────────────────────────────────┤
│ 10:24:12 ERROR python sidecar exited code=1 │ ← Log panel 照常顯示
│ 10:24:13 ERROR ... │
│ ... │
├───────────────────────────────────────────────────────────────────┤
```
### 6.2 元件
| 元素 | 類型 | 細節 |
|------|------|------|
| Banner 容器 | `<div role="alert">` | 背景 `color.destructive/10`,邊框 1px `color.destructive/30`,圓角 `radius.md`padding 16 |
| 警告 icon | `<svg>` | 20×20`color.destructive` |
| 標題 | `<strong>` | 14 px SemiBold`color.destructive` |
| 說明 | `<p>` | 13 px`color.foreground`,最多 2 行,溢出 `…` |
| `Restart Server` | Button `primary` `sm` | 點擊 → 呼叫內部 restartbanner 轉為 Starting state |
| `View log details ↓` | Button `ghost` `sm` | 點擊 → 自動捲動 log panel 到最後一條 ERROR 行並 flash 2 次 |
| `Report...` | Button `ghost` `sm` | **【hold】** 現階段**先不實作**。待 PM 提供 GitHub Issue repo URL 後再恢復。Design 原意:開預設瀏覽器到 GitHub Issue 新增頁面,預填錯誤摘要 + 環境資訊version、OS、最後 20 行 log**不含檔名 / 裝置 serial** |
**R5-D1 落地OS 原生通知並存)**
Error state 進入時,除了控制台 log panel 上方的 Error banner`role="alert"` 由 Wails WebView 內部顯示)**另外發送一次 OS 原生 non-blocking 通知**。這是 R5-D1 使用者決策:控制台可能被最小化、或在另一個桌面 / 虛擬桌面,使用者不一定會看到 bannerOS 通知作為次要冗餘提醒仍有價值。
| 平台 | 通知機制 | Fallback |
|------|---------|---------|
| macOS | `osascript -e 'display notification "..." with title "visionA-local"'`toast 非 dialog | — |
| Windows | `wailsRuntime.SendNotification`(優先) | `msg *` 命令列 |
| Linux | `notify-send` | `zenity --notification` |
**行為細節**
- 通知內容:`標題 = visionA-local Server Error` / `內文 = {error.title}: {error.description 前 60 字}`
- **non-blocking**:不阻塞 UI不彈 modal與先前 v1 的 `showNativeError()`(給 startup 致命錯誤用的 modal dialog區分
- **不重複發**:同一次 Error state 只發一次通知,使用者按 Restart Server 或 State 變回 Starting 後才重置「本次已發」flag
- **技術實作**:新增 Go 檔案 `visiona-local/notify.go`,函式 `sendCrashNotification(title, body string) error`;對應 TDD v2 `control-panel.md §4.7`
### 6.3 Dismiss 條件
Banner **不可手動關閉**(避免使用者忽略問題)。只有下列條件自動消失:
- 使用者按 `Restart Server` 且 server 成功進入 `Running`
- 使用者手動修復環境後按 Start 成功
---
## 7. 啟動行為(對應 R5-4 / R5-D3 / R5-E
### 7.1 預設流程v2.1 修訂)
**v2.1 重要變更**Starting 狀態下控制台顯示**階段化啟動進度面板**(見 `v2/startup-progress.md`),不是只有一顆 spinner。下述「step」對應進度面板的 6 個階段。
```
1. 使用者雙擊 visionA-local.app
2. 控制台視窗開(螢幕中央 / 上次位置)
3. 控制台進入 Starting 狀態log panel 上方顯示「啟動進度面板」
→ 階段 1初始化控制台
→ 階段 2檢查 Python runtime 與驅動
→ 階段 3啟動本機伺服器等 /api/health 200
→ 階段 4偵測 Kneron 裝置
4. Server ready階段 3 完成訊號 = /api/health 200
5. 【每次 / Settings 為 ONmacOS/Windows 預設Linux 預設 OFF
階段 5「開啟瀏覽器」觸發 OS open browser
- macOS: `open http://127.0.0.1:3721/`
- Windows: `start http://127.0.0.1:3721/`
- Linux: `xdg-open http://127.0.0.1:3721/`
【Toggle OFF 時】階段 5 標記為「跳過(依偏好設定)」,不執行 OS open但仍推進
6. 階段 6等待 Web UI 連線
(等 WebSocket hub 收到第一個 client 連線R5-E6 決策)
【Toggle OFF 時】此階段改為「等待使用者手動點擊『在瀏覽器開啟』」
7. 所有 6 階段完成
→ 啟動進度面板淡出fade-out 200 ms
→ Status: Running
→ Status text 顯示 `Running · Browser opened` 10 秒Toggle ON 時)
或純 `Running`Toggle OFF 時,使用者尚未手動 Open
控制台留在背景(不最小化、不關閉)
8. 瀏覽器 tab 進入 Next.js First-Run wizard見 v2.4
```
**R5-D3 重點****每次**啟動(每次 Wails App process 新啟動)都會跑完整 6 階段流程並觸發 OS open不是只有首次。**Restart Server**(同一個 Wails process 內重啟 server不會重開瀏覽器 tab — 由 Offline Overlay 的自動重連處理(見 `v2/server-offline-overlay.md`)。
### 7.2 視覺回饋
第 6 步的 `Running · Browser opened` 是使用者看到控制台第一個確認 server 就緒的訊號。具體視覺:
- Status dot 綠色
- Status text 後方 fade-in 一個 ✓ icon`color.success`
- Text 改為 `Running · Browser opened`
- 10 秒後 ✓ icon 淡出text 縮為 `Running`
### 7.3 例外情境
| 情境 | 控制台行為 |
|------|----------|
| Server `Starting` 超過 5 秒 | 進入 Error state見 §6**不開瀏覽器** |
| Port 3721 被佔 | Server fallback 到 3722 / 3723Header 顯示 `Port: 3722 (default 3721 in use)`,瀏覽器開的 URL 同步換 |
| Settings「自動開瀏覽器」= OFF | Server `Running` 後不做 auto-open使用者需手動點 `Open in Browser` |
---
## 8. 深色模式處理
控制台深色模式**與 Web UI 同步**,機制:
- 讀取 OS 偏好:控制台是 Wails WebView直接用 CSS `prefers-color-scheme`
- CSS 變數切換:和 `frontend/src/app/globals.css` 用一樣的 `:root` / `[data-theme='dark']` block
- 不提供手動切換v1 決策延續)
**Dark 下額外考量**
- Log panel 背景 `oklch(0.18 0 0)`(比 surface 再暗 5%,模仿 terminal
- ERROR 紅色在 dark 下用 `oklch(0.72 0.19 25)`(避免過亮刺眼)
- 圓點狀態色全部用 dark variant確保 4.5:1 對比R4-3 降為盡力而為,但狀態色這種 critical 信號仍維持嚴格)
---
## 9. i18n key 清單(新元件)
控制台文字走 `desktop-control` namespace**獨立於** Next.js Web UI 的 i18n 檔(但抽自同一份辭典,避免兩處維護)。
### 9.1 新增 keyzh-TW / en
| Key | zh-TW | en |
|-----|-------|----|
| `control.title` | visionA-local · 伺服器控制台 | visionA-local · Server Control |
| `control.status.starting` | 啟動中... | Starting... |
| `control.status.running` | 執行中 | Running |
| `control.status.runningBrowserOpened` | 執行中 · 已開啟瀏覽器 | Running · Browser opened |
| `control.status.stopping` | 停止中... | Stopping... |
| `control.status.stopped` | 已停止 | Stopped |
| `control.status.error` | 錯誤:{reason} | Error: {reason} |
| `control.meta.port` | 連接埠 | Port |
| `control.meta.portFallback` | 連接埠:{port}(預設 {default} 被佔用) | Port: {port} (default {default} in use) |
| `control.meta.uptime` | 執行時間 | Uptime |
| `control.meta.pid` | 程序 ID | PID |
| `control.meta.version` | 版本 | Version |
| `control.action.openBrowser` | 在瀏覽器開啟 | Open in Browser |
| `control.action.start` | 啟動 | Start |
| `control.action.manage` | 管理 | Manage |
| `control.action.stopServer` | 停止伺服器 | Stop server |
| `control.action.restartServer` | 重新啟動伺服器 | Restart server |
| `control.log.followTail` | 自動跟隨最新 | Follow tail |
| `control.log.showTimestamps` | 顯示時間戳 | Show timestamps |
| `control.log.filterPlaceholder` | 過濾 log... | Filter... |
| `control.log.jumpToLatest` | 跳到最新 | Jump to latest |
| `control.log.clear` | 清空 | Clear |
| `control.log.clearToast` | 已清空 log可復原 | Log cleared (undo) |
| `control.log.copy` | 複製 | Copy |
| `control.log.copyPrivacyHint` | Log 可能包含檔名與裝置資訊,請注意分享對象 | Log may contain filenames and device info. Share with care. |
| `control.log.export` | 匯出 log | Export log |
| `control.log.openFolder` | 開啟 log 資料夾 | Open log folder |
| `control.log.lines` | 行數:{current} / {max} | Lines: {current} / {max} |
| `control.footer.closeWarning` | ⚠ 關閉此視窗會停止伺服器 | ⚠ Closing this window will stop the server |
| `control.error.title` | 伺服器無法啟動 | Server failed to start |
| `control.error.description` | {具體原因} | {reason} |
| `control.error.restartButton` | 重新啟動伺服器 | Restart Server |
| `control.error.viewLogDetails` | 檢視 log 詳情 | View log details |
| `control.error.reportButton` | 回報問題... | Report... |
### 9.2 刪除 key從現有 Next.js i18n 砍)
`v2/source-selector-update.md §3.2`
---
## 10. 無障礙考量
| 項目 | 設計 |
|------|------|
| Keyboard navigation | 所有 interactive 元素 `tabindex` 合理序列Open in Browser → Start/Manage → Follow tail → Show timestamps → Filter → (log panel 可選取) → Clear → Copy → Export → Open folder |
| Focus ring | 沿用 Web UI token `ring.2 · color.primary`2 px outline-offset |
| Keyboard shortcut | `⌘F` / `Ctrl+F` 聚焦 filter`⌘C` / `Ctrl+C` 複製選取 log`⌘W` / `Ctrl+W` 關視窗R5-2結束 server |
| `⌘Q` | macOS 原生結束 app停 server + quit |
| Screen reader | Status indicator `<span role="status" aria-live="polite">`Error banner `<div role="alert">`log panel `<output aria-live="polite" aria-atomic="false">`(新行 append |
| ARIA label | 所有 icon button 有 `aria-label`(例如 Follow tail checkbox 的 trailing spinner 有 `aria-label="Auto-scrolling enabled"` |
| 色彩對比 | Status dot / ERROR level log / Error banner 強制 ≥ 4.5:1即使 A11y 整體降級為「盡力而為」critical 信號不妥協) |
| Reduced motion | `prefers-reduced-motion: reduce` → 關閉 spinner 旋轉(改為靜態點)、關閉 log 滑入動畫、關閉 Browser opened fade |
| 字級可縮放 | 使用 `rem` 而非 `px` 定義字級,支援 OS 字級偏好 |
---
## 11. 與 v1`design-analysis-round2-refactor.md`)的差異
| 面向 | v1 分析稿 | v2 正式規格 |
|------|----------|------------|
| 視窗職責 | 三方尚在討論 | 確定為雙 UIR5-1 |
| 關閉行為 | 待 D1 決策 | **關閉 = 結束 server**footer 持久提示R5-2 |
| Tray | 建議復活 tray | **不做**R5-3 |
| 首次啟動 | 建議自動開瀏覽器 | 採納自動開瀏覽器R5-4 |
| Primary controls | 4 顆 | 3 顆Stop/Restart 併入 Manage 下拉) |
| Header 狀態列 | 固定 `Running` | 首次啟動後動態 `Running · Browser opened` 10 秒 |
| Error 狀態視覺 | 未設計 | 新增 Error banner§6 |
| Mock 切換 | 未納入 scope | **明確砍除**R5-5a |
| Log 上限 | 1000 行 | 1000 行(維持) |
| Log 寫檔 | 7 天 / 10MB rotate | 維持 |
| 語系 | 跟隨系統 | 跟隨系統(維持) |
---
## 12. v2 → v2.1 Diff2026-04-14
| # | 位置 | v2 | v2.1 | 來源 |
|---|------|----|----|------|
| 1 | §4.4 Log panel 最大行數 | 1000 行 | **2000 行**(對齊 TDD v2 ring buffer | Architect Review Minor m-1 |
| 2 | §4.4 Log 寫檔 | rotate 7 天 / 10 MB | **無落地寫檔**in-memory ring buffer使用者透過 Export log 手動匯出) | Architect Review Minor m-12 |
| 3 | §4.6 Footer 行數顯示 | `Lines: {current} / 1000` | `Lines: {current} / 2000` | Architect Review Minor m-1 |
| 4 | §5 狀態機 | Starting 只有 spinner1-5 秒 | Starting 顯示**階段化啟動進度面板**4-15 秒(上限 60 秒R5-E1 | R5-E |
| 5 | §6.2 Error banner | `Report...` 正常按鈕 | **【hold】** 待 PM 提供 GitHub Issue repo URL 後再恢復 | Architect Review Minor m-11 / G-3 |
| 6 | §6.2 新增 | — | **R5-D1 OS 原生通知並存**Error state 發 non-blocking toast notification不是 modal | R5-D1 / Architect Review Minor m-4 |
| 7 | §7.1 第 5 步 | 「首次 / Settings 為 ON」 | **「每次 / Settings 為 ON」**,新增 Linux 預設 OFF 說明,流程改為 6 階段化 | R5-D3 + R5-E |
| 8 | §7.1 新增 | — | 引用新檔 `v2/startup-progress.md`R5-E 階段化啟動進度面板) | R5-E |
---
**下一步**:交 M8-5 Frontend Agent 實作Wails 控制台 + 啟動進度面板),交 Reviewer 審查 control-panel.md + startup-progress.md 整體一致性。