jim800121chen 46514d77d7 docs(local-tool): M9 — Kneron Dongle FW 偵測 + 升降版(A+B、翻案 R5-Q9)
L 級新功能、PRD/Design/TDD/ADR 三方協作 + 互審 + M9-6 SDK 雙驗證、總計 ~9000 行文件。

範圍:
- A 階段(MVP、5 人天):KL520 + KL720 自動升級 KDP1 → KDP2
- B 階段(10.5 人天):手動降版面向一般使用者 + KL630 / KL730 擴展
- 合計 15.5 人天、安裝包 +7MB(保守 bundle 策略)

關鍵決策:
- 翻案 R5-Q9(progress.md 第二輪使用者決策「韌體燒錄 flash → B 砍掉」)
- 跨平台用 KneronPLUS Python C API、不用 DFUT.exe
- 多版本目錄結構選 C metadata(firmware/<chip>/{version}/ + CURRENT_VERSION)
- Kneron firmware redistribution 授權與 R5-B4 預置模型同性質、發佈前評估

文件產出:
- PRD v2.2(PRD-v2.md 495 行 + features/feature-firmware-management.md 599 行)
- Design v2.2(firmware-management.md 948 行 + control-panel.md §6a graceful shutdown)
- TDD v2.2(v2/firmware-management.md 823 行 + ADR-001 218 行)
- 8 份 research(含 M9-6 弱驗證 + 強驗證、~3200 行)
- 3 份三方互審報告(PM/Design/Architect cross-review)

M9-6 強驗證重大發現(影響 B 階段):
- KL730 product_id 實際是 0x732(不是 0x0730)
- KL630/KL730 firmware 是 embedded Linux rootfs(不是 .bin、不同代設計)
- KneronPLUS Python 沒 update_kdp_firmware_from_files 公開 API、warrenchen 走 ctypes
- 不影響 A 階段、B 階段 M9-8 需 spike

下一步:派 backend M9-1 起跑(bridge.py handle_firmware_upgrade)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 07:40:56 +08:00

41 KiB
Raw Blame History

v2.1 — Wails Server Control Panel 設計規格

本章對應 R5-1 / R5-2 / R5-3 / R5-4 / R5-5 / R5-5a / R5-D1 / R5-D3 / R5-Ev2.1 補丁) 上層索引:../design-spec-v2.md 版本:v2.1 · 更新日期2026-04-14 相關:v2/startup-progress.mdR5-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 serverRestart 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.monoSF Mono / Consolas / Menlo
字級 12 px
行高 1.5
背景 color.surface-1Lightoklch(0.99 0 0)Darkoklch(0.18 0 0)
選取背景 color.primary/20
最大行數 2000ring buffer超過舊的 drop對齊 TDD v2 Go server 常數,~400KB 記憶體可忽略)
寫檔 TDD v2 採 in-memory ring bufferlog 不落地;使用者若需保存用 Export log 手動匯出)
滑入動畫 60 ms fade-inprefers-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} / 200012px muted
關閉提示(預設) ⚠ Closing this window will stop the server.12px muted
關閉提示韌體進行中、v2.2 新增) 🚫 韌體更新中、關閉視窗可能造成裝置損毀12px color.destructive SemiBold取代預設提示對應 i18n key control.footer.closeWarningFirmwareActive、詳見 §6a.8

持久提示為什麼不彈 modalR5-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
運行階段/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
Primary controls 全部 disabled
Running color.success RunningRunning · Browser openedToggle 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 bannerlog 面板不消失。

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.mdpadding 16
警告 icon <svg> 20×20color.destructive
標題 <strong> 14 px SemiBoldcolor.destructive
說明 <p> 13 pxcolor.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 bannerrole="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 成功

6a. 韌體進行中關閉攔截v2.2 新增、對應 firmware-management §14.4 第 6 點)

6a.1 為什麼需要這個

依 R5-2「關閉控制台 = 結束 server」但有一個例外場景韌體升級 / 切換進行中

  • 韌體切換 / 降版是寫 flash 的破壞性操作、中斷會造成裝置永久損毀brick
  • 如果使用者在韌體切換進行中關掉 Wails 控制台、Wails close handler 預設會送 SIGTERM 給 server 結束 Python sidecar
  • Python sidecar 正在 update_kdp_firmware_from_files 中段被砍 = flash 寫一半就停 = brick

依 Architect TDD §8.6v2.2 新增、server 已實作「降版進行中拒絕 graceful shutdown」邏輯

  • server 收到 SIGTERM → 檢查 firmware.Service.HasActiveTask()
  • 有 active task → 拒絕 shutdown、回傳 firmwareInProgress: true 給 Wails close handler
  • 沒 active task → 正常 graceful shutdown既有 7+1s pattern

控制台側需提供對應 UI 攔截關閉動作、警告使用者風險。

6a.2 觸發時機

下列任一情況、使用者試圖關閉 Wails 控制台時:

  • 按視窗右上角 × 關閉鈕
  • ⌘W / Ctrl+W 鍵盤快捷鍵
  • ⌘Q / 系統選單「結束」macOS/ 系統匣「結束」Windows
  • 強制最小化 + 結束程式(透過 dock / taskbar

→ 控制台先 query server /api/firmware/status(或對應 IPC method

  • 回傳 {active: false} → 正常走 R5-2 流程(結束 server + 關視窗)
  • 回傳 {active: true, taskInfo: {deviceName, stage, etaSeconds}} → 觸發本節攔截 modal

6a.3 攔截 Modal Wireframe

┌─ ⚠ 韌體更新進行中 ───────────────────────────────────────┐
│                                                             │
│   🚫 強制關閉可能造成裝置永久損毀                            │
│                                                             │
│   KL520 #1 的韌體切換正在進行中。                          │
│   現在強制關閉應用程式會中斷韌體寫入、可能造成裝置         │
│   永久損毀brick、無法救援。                            │
│                                                             │
│   ┌──────────────────────────────────────────────┐         │
│   │  目前階段:寫入韌體(階段 3 / 4            │         │
│   │  預估剩餘:約 22 秒                          │         │
│   │  ████████████░░░░  60%                       │         │
│   └──────────────────────────────────────────────┘         │
│                                                             │
│   ⚠ 建議「繼續等待」、約 22 秒後即可安全關閉。              │
│                                                             │
│                  [ 繼續等待 ]    [ 強制關閉 ]              │
│                                                             │
└─────────────────────────────────────────────────────────────┘

6a.4 元件規格

元素 類型 樣式
Modal 容器 <div role="alertdialog" aria-modal="true"> 寬度 520px、color.card 背景、color.destructive 2px 邊框、elevation.5 陰影
標題 icon <svg> 24×24、color.destructive
標題 <h2> 18px / SemiBold / color.destructive、「⚠ 韌體更新進行中」
主要警語 <strong> 16px / Bold / color.destructive、「🚫 強制關閉可能造成裝置永久損毀」
說明段落 <p> 14px / regular / color.foreground
進度資訊區 <div> 背景 color.muted/30、padding 16、圓角 radius.md
階段文字 <span> 13px / medium / color.foreground
預估剩餘 <span> 13px / medium / color.foreground、每秒更新(從 server WebSocket progress event 推送)
進度條 <progress><div role="progressbar"> 高 8px、color.destructive fill強調危險color.muted track、圓角 radius.full
建議文字 <p> 13px / regular / color.warning、附 ⚠ icon
「繼續等待」按鈕 Button primary md 預設聚焦focus、Enter 觸發、樣式為主要 CTA
「強制關閉」按鈕 Button destructive ghost md 邊框 color.destructive、文字 color.destructive、背景透明(不主動誘導點擊)

6a.5 互動行為

「繼續等待」(建議路徑):

  • 點擊 / Enter → 關閉 modal、保留控制台視窗
  • 控制台留在原狀態韌體進度面板繼續顯示、log 持續更新)
  • 不結束 server、不關 Wails 視窗
  • 韌體任務完成後、firmware-management §5.4 / §6.4 toast 出現、使用者可以再次嘗試關閉視窗(這次走正常 R5-2 流程)

「強制關閉」(危險路徑):

  • 點擊 → 不立即關閉、跳出第二層最終確認 modal
    ┌─ 🚫 確定要強制關閉? ────────────────────────────────┐
    │                                                       │
    │   強制關閉會:                                        │
    │   • 中斷正在進行的韌體寫入                            │
    │   • 可能造成裝置永久損毀(無法救援)                  │
    │   • 即使重新插拔也無法復原                            │
    │                                                       │
    │   請輸入「FORCE」確認你了解後果                     │
    │   ┌──────────────────────────────────────────┐       │
    │   │                                          │       │
    │   └──────────────────────────────────────────┘       │
    │   (大小寫需完全相同)                                  │
    │                                                       │
    │                  [ 返回 ]    [ 確認強制關閉 ]         │
    │                                                       │
    └───────────────────────────────────────────────────────┘
    
  • 「FORCE」輸入框比對與 firmware-management §6.1 「DOWNGRADE」相同邏輯嚴格 === 比對)
  • 「確認強制關閉」按鈕在輸入 FORCE 前 disabled、輸入正確後變為 destructive 紅色
  • 點擊 → Wails close handler 跳過 server graceful shutdown 流程、直接 kill server process傳 SIGKILL+ 關閉視窗

ESC 鍵:等同點「繼續等待」(安全路徑)、關 modal 保留視窗

外部點擊:不可關閉(捕獲事件、抖動 modal 200ms 提示)、與 firmware-management §6.1 同設計

6a.6 視覺強度層級對比

元素 firmware-management §6.1 降版二次確認 本節 §6a.3 韌體進行中關閉攔截
場景 降版開始前 降版進行中 + 使用者試圖中斷
風險等級 中(降版本身有 brick 風險、但 < 1% 高(中斷正在寫 flash = 高機率 brick
確認字串 DOWNGRADE(指向操作意圖) FORCE(指向強制中斷)
Modal 邊框 1px 2px(更強烈)
預設聚焦 「DOWNGRADE」input 「繼續等待」按鈕(引導安全路徑)
安全 CTA 「取消」ghost 「繼續等待」primary(主要 CTA、強引導
危險 CTA 「確認切換」destructive filledinput 通過後) 「強制關閉」destructive ghost(不主動誘導、需二次確認)

6a.7 不重複攔截

  • 同一個關閉嘗試只跳一次 modal
  • 使用者選「繼續等待」後、韌體完成前如再次嘗試關閉 → 重新跑 6a.2 流程(再次 query server status
  • 「強制關閉」確認後、modal 立即關閉、不留任何撤銷機會(已通過二次確認)
  • §4.6 「⚠ Closing this window will stop the server.」footer 提示仍然顯示
  • 韌體進行中時、footer 提示改為更嚴重的紅色版本:「🚫 韌體更新中、關閉視窗可能造成裝置損毀」
  • 對應 i18n keycontrol.footer.closeWarningFirmwareActive

6a.9 i18n keys新增

Key zh-TW en
control.firmwareBlock.title 韌體更新進行中 Firmware update in progress
control.firmwareBlock.heading 🚫 強制關閉可能造成裝置永久損毀 🚫 Force-close may permanently damage the device
control.firmwareBlock.description {deviceName} 的韌體{operation}正在進行中。現在強制關閉應用程式會中斷韌體寫入、可能造成裝置永久損毀brick、無法救援。 {operation} on {deviceName} is in progress. Force-closing the application now will interrupt the firmware write and may permanently damage (brick) the device beyond recovery.
control.firmwareBlock.operation.upgrade 升級 upgrade
control.firmwareBlock.operation.switch 切換 switch
control.firmwareBlock.stage 目前階段:{stage}(階段 {n} / {total} Current stage: {stage} (stage {n} / {total})
control.firmwareBlock.eta 預估剩餘:約 {seconds} 秒 Estimated remaining: ~{seconds}s
control.firmwareBlock.recommendation ⚠ 建議「繼續等待」、約 {seconds} 秒後即可安全關閉。 ⚠ Recommended: "Continue waiting". Safe to close in about {seconds}s.
control.firmwareBlock.action.continueWaiting 繼續等待 Continue waiting
control.firmwareBlock.action.forceClose 強制關閉 Force close
control.firmwareBlock.confirm.title 🚫 確定要強制關閉? 🚫 Confirm force close?
control.firmwareBlock.confirm.risk.interrupt 中斷正在進行的韌體寫入 Interrupt the ongoing firmware write
control.firmwareBlock.confirm.risk.brick 可能造成裝置永久損毀(無法救援) May permanently damage the device (unrecoverable)
control.firmwareBlock.confirm.risk.noRecover 即使重新插拔也無法復原 Cannot be recovered even by unplugging and reconnecting
control.firmwareBlock.confirm.prompt 請輸入「FORCE」確認你了解後果 Type "FORCE" to confirm you understand the consequences:
control.firmwareBlock.confirm.placeholder FORCE FORCE
control.firmwareBlock.confirm.helpText (大小寫需完全相同) (case-sensitive)
control.firmwareBlock.confirm.action.back 返回 Back
control.firmwareBlock.confirm.action.forceClose 確認強制關閉 Confirm force close
control.footer.closeWarningFirmwareActive 🚫 韌體更新中、關閉視窗可能造成裝置損毀 🚫 Firmware update in progress — closing window may damage device

合計新增 19 個 i18n keyszh-TW / en 各一套)。

6a.10 無障礙考量

項目 設計
Modal 結構 role="alertdialog"、screen reader 主動朗讀
Focus management modal 開啟時自動 focus 「繼續等待」按鈕、focus trap
Tab 順序 「繼續等待」→「強制關閉」
ESC 鍵 等同「繼續等待」(安全路徑)
危險視覺不單靠顏色 標題附 ⚠ icon + 「強制關閉」按鈕附文字描述(不只是紅色)
Reduced motion prefers-reduced-motion: reduce → modal 動畫 0ms、進度條改靜態顯示
觸控目標 兩個按鈕 ≥ 44×44px
對比 紅色 destructive 對 card 背景 ≥ 4.5:1WCAG AA、critical 信號不妥協)

6a.11 與 Architect TDD §8.6 的銜接

  • TDD §8.6 提供 HasActiveTask() method 與 server graceful shutdown 拒絕邏輯
  • 控制台側透過既有 IPC或新增一個 /api/firmware/status 端點)查詢 active task
  • 取得 active task 資訊(deviceName / stage / etaSeconds / direction)後渲染本節 modal
  • 「強制關閉」確認後、控制台需呼叫 server force-shutdown IPC methodbypass graceful shutdown、server 收到後直接送 SIGKILL 給 Python sidecar、不等 firmware task 完成
  • 詳細 IPC 規格依 TDD §8.6 規範Frontend M8-5 階段對接)

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 一個 ✓ iconcolor.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.primary2 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. 與 v1design-analysis-round2-refactor.md)的差異

面向 v1 分析稿 v2 正式規格
視窗職責 三方尚在討論 確定為雙 UIR5-1
關閉行為 待 D1 決策 關閉 = 結束 serverfooter 持久提示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.mdR5-E 階段化啟動進度面板) R5-E

13. v2.1 → v2.2 Diff2026-05-25

# 位置 v2.1 v2.2 來源
1 §6a 新增整節 §6a 韌體進行中關閉攔截(兩層 modal + 「FORCE」二次確認 + 19 個 i18n keys + 6a.8 footer 紅色變體) firmware-management §14.4 第 6 點 + Architect TDD §8.6 + Design 三方互審吸收

下一步:交 M8-5 Frontend Agent 實作Wails 控制台 + 啟動進度面板 + 韌體進行中關閉攔截 modal交 Reviewer 審查 control-panel.md + startup-progress.md + firmware-management.md 整體一致性。