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

418 lines
26 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.6 — 啟動進度面板Startup Progress Panel
> 本章對應 **R5-E1 ~ R5-E6**perceived performance 階段化啟動)。
> 上層索引:`../design-spec-v2.md`
> 相關:`v2/control-panel.md §5`(狀態機 Starting state、`v2/control-panel.md §7`(啟動流程)
> 版本:**v2.1**(新增章節)· 建立日期2026-04-14
---
## 1. 定位與動機
**問題**v2 第一版把 AC-1.3 定為「10 秒上限」硬指標,但 Architect §11-2 分析估計樂觀 ~4 秒 / 悲觀 ~8 秒 / Windows + Defender 首次掃描最壞 ~11 秒。強求 10 秒會卡 Windows 使用者。
**使用者決策R5-E**:把問題從「要多快」翻轉成「**讓使用者感覺進度有在推動**」,採 Nielsen Norman *perceived performance* 原則 —
> 使用者能忍受 60 秒,只要每一秒都有視覺反饋。使用者不能忍受 10 秒,如果其中 8 秒是白畫面。
**本章職責**:設計 Starting state 時浮在 log panel 上方的**階段化啟動進度面板**,讓使用者知道:
- 現在在做什麼(階段編號 + 名稱 + 描述)
- 進度到哪裡6 階段的哪一階,視覺進度條)
- 快 OK 了 vs 卡住了(超時提示)
- 出錯了怎麼辦Error state 三個救援按鈕)
---
## 2. Wireframe
### 2.1 正常啟動中(階段 3「啟動本機伺服器」進行中
```
├───────────────────────────────────────────────────────────────────┤
│ ☑ Follow tail ☑ Show timestamps 🔍 [ Filter ... ] │
├───────────────────────────────────────────────────────────────────┤
│ ┌───────────────────────────────────────────────────────────────┐ │
│ │ 正在啟動 visionA-local · Starting visionA-local │ │
│ │ │ │
│ │ ✅ 1 · 初始化控制台 完成 │ │
│ │ Initializing control panel │ │
│ │ │ │
│ │ ✅ 2 · 檢查 Python 執行環境 完成 │ │
│ │ Checking Python runtime │ │
│ │ │ │
│ │ 🔄 3 · 啟動本機伺服器 (spinner) │ │
│ │ Starting local server... │ │
│ │ │ │
│ │ ⏳ 4 · 偵測 Kneron 裝置 等待中 │ │
│ │ Detecting Kneron devices │ │
│ │ │ │
│ │ ⏳ 5 · 開啟瀏覽器 等待中 │ │
│ │ Opening browser │ │
│ │ │ │
│ │ ⏳ 6 · 等待 Web UI 連線 等待中 │ │
│ │ Waiting for Web UI to connect │ │
│ │ │ │
│ │ ▰▰▰▰▰▰▱▱▱▱▱▱▱▱▱▱▱▱▱▱▱▱▱▱ 進度 3 / 6 │ │
│ └───────────────────────────────────────────────────────────────┘ │
├───────────────────────────────────────────────────────────────────┤
│ 10:23:40 INFO HTTP server binding on 127.0.0.1:3721 │ ← log panel
│ 10:23:41 INFO wails ipc ready │ 照常顯示
│ ... │
├───────────────────────────────────────────────────────────────────┤
```
### 2.2 階段卡超過 20 秒Retry HintR5-E3
```
│ ┌───────────────────────────────────────────────────────────────┐ │
│ │ 正在啟動 visionA-local · Starting visionA-local │ │
│ │ │ │
│ │ ✅ 1 · 初始化控制台 完成 │ │
│ │ ✅ 2 · 檢查 Python 執行環境 完成 │ │
│ │ │ │
│ │ 🔄 3 · 啟動本機伺服器 (spinner) │ │
│ │ Starting local server... │ │
│ │ ⚠ 這個步驟花的時間比預期久,正在重試... │ │
│ │ This step is taking longer than expected, retrying... │ │
│ │ │ │
│ │ ⏳ 4 · 偵測 Kneron 裝置 等待中 │ │
│ │ ... │ │
│ │ │ │
│ │ ▰▰▰▰▰▰▱▱▱▱▱▱▱▱▱▱▱▱▱▱▱▱▱▱ 進度 3 / 6 · 已等待 22 秒 │ │
│ └───────────────────────────────────────────────────────────────┘ │
```
### 2.3 Error 狀態R5-E460 秒總上限或任一階段失敗)
```
│ ┌───────────────────────────────────────────────────────────────┐ │
│ │ ❌ 啟動失敗 · Startup failed │ │
│ │ │ │
│ │ 啟動時間超過 60 秒,可能是系統環境異常或網路中斷。 │ │
│ │ Startup exceeded 60 seconds. Your environment may have │ │
│ │ issues or the network is interrupted. │ │
│ │ │ │
│ │ 失敗階段3 · 啟動本機伺服器 │ │
│ │ Failed stage: 3 · Starting local server │ │
│ │ │ │
│ │ [ 🔄 重試 Retry ] [ 📋 檢視 log View Log ] │ │
│ │ [ 🐞 回報問題 Report Issue (hold) ] │ │
│ └───────────────────────────────────────────────────────────────┘ │
```
---
## 3. 元件規格
### 3.1 Panel 容器
| 屬性 | 值 |
|------|----|
| Root element | `<section role="progressbar" aria-valuemin="0" aria-valuemax="6" aria-valuenow="{currentStage}" aria-label="{i18n:startup.panel.ariaLabel}">` |
| 位置 | log controls 下方、log panel 上方(和 Error banner 同一個 slot |
| 背景 | `color.surface-1` |
| 邊框 | 1 px `color.border` |
| 圓角 | `radius.md`(沿用 tokens |
| Padding | 16 px |
| Max width | 控制台 content 寬度(約 688 px |
| 進入動畫 | fade-in 200 ms`prefers-reduced-motion` 時跳變) |
| 收尾動畫 | 所有階段完成後 fade-out 200 ms之後 unmount |
### 3.2 Panel header
| 元素 | 類型 | 樣式 |
|------|------|------|
| 標題 | `<h2>` | 14 px SemiBold`color.foreground` |
| 文字zh-TW | `正在啟動 visionA-local` | |
| 文字en | `Starting visionA-local` | |
**i18n key**`startup.panel.title.zh` / `startup.panel.title.en`
### 3.3 階段列 `<StageItem>`
每個階段是一個獨立列,包含:
| 子元素 | 類型 | 內容 | 狀態變化 |
|------|------|------|---------|
| Icon圓圈 | 20×20 px `<span>` | ✅(完成)/ 🔄(進行中,旋轉 spinner/ ⏳(等待)/ ❌(失敗) | 見 §3.4 狀態對照 |
| 階段編號 | `<span>` | `{n}` `·` | 14 px Mediummuted-foreground |
| Label雙語併呈 | `<div>` | 第一行zh-TW 14 px第二行en 12 px muted-foreground | 見 §4 文案 |
| 狀態標籤 | `<span>` | 完成 / 進行中 / 等待中 / 失敗 | 右對齊 |
**範例結構**
```html
<div class="stage-item" data-state="running">
<span class="stage-icon" aria-hidden="true">
<Spinner />
</span>
<span class="stage-number">3 ·</span>
<div class="stage-label">
<div class="stage-label-primary">啟動本機伺服器</div>
<div class="stage-label-secondary">Starting local server</div>
</div>
<span class="stage-status">進行中</span>
</div>
```
### 3.4 階段狀態對照
| 資料狀態 | Icon | Icon 顏色 | Label 顏色 | 狀態文字zh / en |
|---------|------|----------|-----------|------------------|
| `pending` | ⏳ (outline circle) | `color.muted-foreground` | `color.muted-foreground` | `等待中` / `Waiting` |
| `running` | 🔄 旋轉 spinner 16 px | `color.primary` | `color.foreground` Bold | `進行中` / `Running` |
| `running-slow` | 🔄 旋轉 spinner + ⚠ 小圖示 | `color.warning` | `color.foreground` Bold | `正在重試...` / `Retrying...` |
| `done` | ✅ filled check | `color.success` | `color.muted-foreground`(略淡) | `完成` / `Done` |
| `failed` | ❌ filled cross | `color.destructive` | `color.destructive` Bold | `失敗` / `Failed` |
| `skipped` | ⏭ filled skip | `color.muted-foreground` | `color.muted-foreground` 斜體 | `跳過(依偏好設定)` / `Skipped (per preference)` |
**動畫**
- pending → runningspinner fade-in 150 ms
- running → donespinner → check icon 交叉淡入 200 ms整行 label 漸變淡
- running → running-slow⚠ icon slide-in-left 200 ms
- running → failedspinner → ❌ icon整行背景 `color.destructive/5` 淡入
- `prefers-reduced-motion: reduce` → 全部 fade 降為 0 ms 跳變spinner 改為靜態點
### 3.5 進度條
| 屬性 | 值 |
|------|----|
| 類型 | 6 格離散進度條(不是連續 bar每格對應一階段 |
| 已完成格 | `color.success` 填滿 |
| 進行中格 | `color.primary` 填滿 + 脈衝動畫opacity 0.6 ↔ 1.0, 1.5 s |
| 等待中格 | `color.border` 空格 |
| 失敗格 | `color.destructive` 填滿 |
| 高度 | 6 px |
| 格間距 | 2 px |
| 附加文字 | 右側 `進度 {current} / 6` + 卡超 20 秒時附 `· 已等待 {elapsed} 秒` |
ARIA`<div role="progressbar" aria-valuenow="{current}" aria-valuemax="6" aria-label="啟動進度">`
### 3.6 Retry HintR5-E3
當任一階段 `running` 狀態超過 **20 秒**未變 `done`,該 StageItem 下方浮出 hint line
| 屬性 | 值 |
|------|----|
| 觸發 | `stage.state === 'running' && (now - stage.startedAt) > 20_000ms` |
| 隱藏 | 階段狀態變 `done``failed` 後隨 StageItem 一起消失 |
| 顏色 | `color.warning` text |
| Icon | ⚠ 14×14 |
| 雙語 | 第一行中文、第二行英文12 px muted |
**文案**
- zh`這個步驟花的時間比預期久,正在重試...`
- en`This step is taking longer than expected, retrying...`
**i18n key**`startup.timeout.message.zh` / `startup.timeout.message.en`
### 3.7 Error StateR5-E4
任一階段 `failed` 或總計超過 **60 秒** → Panel 整體換為 Error mode
- StageItem 列表隱藏(只保留失敗的那一階段顯示為 ❌)
- 進度條換成 Error 樣式(整條 `color.destructive/20` 背景)
- 大標題 `啟動失敗 / Startup failed`
- 說明文字(雙語)
- 三顆按鈕:
| 按鈕 | 類型 | 行為 |
|------|------|------|
| 🔄 重試 / Retry | Button `primary` `md` | 重置進度面板,重新跑階段 1 |
| 📋 檢視 log / View Log | Button `ghost` `md` | 收起 panelfocus 到 log panel 最後一條 ERROR 行flash 2 次 |
| 🐞 回報問題 / Report Issue **【hold】** | Button `ghost` `md` | **現階段不實作**(待 PM 提供 GitHub Issue repo URL |
**R5-D1 OS 原生通知並存**:進入 Error state 時同時呼叫 `sendCrashNotification()` 發 OS non-blocking toast`control-panel.md §6.2` 的 Error banner 一致)。
---
## 4. 6 階段文字定版R5-E5 定稿)
| # | 階段 | Label (zh-TW) | Label (en) | Description (zh-TW) | Description (en) | 完成條件(技術訊號) |
|---|------|-------------|------------|--------------------|------------------|------------------|
| 1 | 初始化控制台 | 初始化控制台 | Initializing control panel | 準備 visionA-local 桌面環境 | Preparing visionA-local desktop | Wails `OnStartup` 完成、i18n 載入、面板 mount |
| 2 | 檢查 Python 執行環境 | 檢查 Python 執行環境 | Checking Python runtime | 首次啟動可能需要較長時間 | First launch may take longer | `ensurePythonRuntime()` 回傳 + 驅動檢查通過 |
| 3 | 啟動本機伺服器 | 啟動本機伺服器 | Starting local server | 在 127.0.0.1:3721 啟動服務 | Starting service on 127.0.0.1:3721 | `/api/health` 回 200首次成功 |
| 4 | 偵測 Kneron 裝置 | 偵測 Kneron 裝置 | Detecting Kneron devices | 掃描已連接的硬體 | Scanning connected hardware | Go server 回傳 devices scan 結果(不論有無裝置都算成功) |
| 5 | 開啟瀏覽器 | 開啟瀏覽器 | Opening browser | 在預設瀏覽器開啟 Web UI | Opening the Web UI in your default browser | `OpenInBrowser()` 呼叫完成(不等瀏覽器實際開好) |
| 6 | 等待 Web UI 連線 | 等待 Web UI 連線 | Waiting for Web UI to connect | 正在與瀏覽器建立即時連線 | Establishing realtime connection with the browser | **WebSocket hub 收到第一個 client 連線**R5-E6 |
### 4.1 特殊文案
**階段 2 首次啟動提示(常態)**
- Description 固定顯示「首次啟動可能需要較長時間 / First launch may take longer」
- 這不是超時 hint是預設的 description讓使用者看到就不焦慮即使只花 0.5 秒也顯示)
**階段 5 Linux / Settings OFF 情境**
- 狀態直接從 `pending` 跳到 `skipped`
- 狀態文字顯示「跳過(依偏好設定)/ Skipped (per preference)」
- Icon 用 ⏭
- 進度條該格仍算推進(不當失敗,不擋住階段 6
**階段 6 Settings OFF 情境**
- 狀態從 `pending` 跳到 `running`label description 改為
- zh`請點擊控制台的「在瀏覽器開啟」按鈕`
- en`Please click "Open in Browser" in the Control Panel`
- 當使用者手動點 Open in Browser 並成功建立 WebSocket 連線後 → `done`
- **不套 20 秒 retry hint**(因為是等待人為動作,不是系統卡住)
---
## 5. 成功狀態收尾Running state 轉場)
當階段 6 狀態變 `done`
1. 進度條最後一格填滿 `color.success`,脈衝動畫停止
2. 停留 500 ms 讓使用者看到「全綠」
3. 整個 Panel fade-out 200 ms
4. Panel unmount
5. 控制台 Status 區域變 `Running · Browser opened`Toggle ON 首次)或純 `Running`
6. Primary controls 啟用Open in Browser 等)
**總轉場時間**:約 700 ms500 ms 停留 + 200 ms fade
**`prefers-reduced-motion: reduce`**:省略 500 ms 停留 + 200 ms fade直接 unmount。
---
## 6. 無障礙考量
| 項目 | 設計 |
|------|------|
| **Role** | Panel root `<section role="progressbar" aria-valuemin="0" aria-valuemax="6" aria-valuenow="{current}">` |
| **Live region** | Panel 下加 `<div class="sr-only" aria-live="polite" aria-atomic="true">` 宣告階段變化:`階段 {n}{label}{status}`zh`Stage {n}: {label}, {status}`en |
| **Focus trap** | Panel 顯示期間**不 trap focus**Starting state 使用者不應該需要操作 panel 以外的元素,但允許使用者切換視窗或捲 log panel |
| **Keyboard** | `⌘0` / `Ctrl+0` focus 進度面板第一個可聚焦元素Retry 按鈕或 panel root<br>`Esc`:若 panel 已進入 Error state → 不動作(避免誤關);若進度已跑完正在 fade-out → 立即 unmount |
| **色彩對比** | Stage label / Description / 狀態文字 ≥ 4.5:1failed / running-slow hint ≥ 4.5:1critical 信號不妥協,對齊 control-panel §10 |
| **Reduced motion** | 所有 fade / spinner / 脈衝動畫降為 0 ms 跳變spinner 改為靜態點Retry hint 直接顯示不滑入 |
| **字級可縮放** | 使用 `rem` 定義字級 |
| **Icon 替代文字** | 每個 icon 有 `aria-hidden="true"`(狀態透過 live region 宣告),或 `role="img" aria-label="..."` |
---
## 7. i18n key 清單
全部加到 `desktop-control` namespace 的 `startup.*` 子樹:
| Key | zh-TW | en |
|-----|-------|----|
| `startup.panel.title` | 正在啟動 visionA-local | Starting visionA-local |
| `startup.panel.ariaLabel` | 啟動進度:階段 {current} / {max} | Startup progress: stage {current} / {max} |
| `startup.progressLabel` | 進度 {current} / {max} | Progress {current} / {max} |
| `startup.progressWithElapsed` | 進度 {current} / {max} · 已等待 {elapsed} 秒 | Progress {current} / {max} · {elapsed}s elapsed |
| `startup.stage.1.label` | 初始化控制台 | Initializing control panel |
| `startup.stage.1.description` | 準備 visionA-local 桌面環境 | Preparing visionA-local desktop |
| `startup.stage.2.label` | 檢查 Python 執行環境 | Checking Python runtime |
| `startup.stage.2.description` | 首次啟動可能需要較長時間 | First launch may take longer |
| `startup.stage.3.label` | 啟動本機伺服器 | Starting local server |
| `startup.stage.3.description` | 在 127.0.0.1:{port} 啟動服務 | Starting service on 127.0.0.1:{port} |
| `startup.stage.4.label` | 偵測 Kneron 裝置 | Detecting Kneron devices |
| `startup.stage.4.description` | 掃描已連接的硬體 | Scanning connected hardware |
| `startup.stage.5.label` | 開啟瀏覽器 | Opening browser |
| `startup.stage.5.description` | 在預設瀏覽器開啟 Web UI | Opening the Web UI in your default browser |
| `startup.stage.5.skipped.label` | 跳過(依偏好設定) | Skipped (per preference) |
| `startup.stage.6.label` | 等待 Web UI 連線 | Waiting for Web UI to connect |
| `startup.stage.6.description` | 正在與瀏覽器建立即時連線 | Establishing realtime connection with the browser |
| `startup.stage.6.manualHint` | 請點擊控制台的「在瀏覽器開啟」按鈕 | Please click "Open in Browser" in the Control Panel |
| `startup.status.pending` | 等待中 | Waiting |
| `startup.status.running` | 進行中 | Running |
| `startup.status.done` | 完成 | Done |
| `startup.status.failed` | 失敗 | Failed |
| `startup.status.skipped` | 跳過(依偏好設定) | Skipped (per preference) |
| `startup.timeout.message` | 這個步驟花的時間比預期久,正在重試... | This step is taking longer than expected, retrying... |
| `startup.error.title` | 啟動失敗 | Startup failed |
| `startup.error.description.timeout` | 啟動時間超過 60 秒,可能是系統環境異常或網路中斷。 | Startup exceeded 60 seconds. Your environment may have issues or the network is interrupted. |
| `startup.error.description.stageFailed` | 階段「{stageLabel}」執行失敗。 | Stage "{stageLabel}" failed. |
| `startup.error.failedStage` | 失敗階段:{n} · {label} | Failed stage: {n} · {label} |
| `startup.error.retry` | 重試 | Retry |
| `startup.error.viewLog` | 檢視 log | View Log |
| `startup.error.report` | 回報問題 | Report Issue |
| `startup.liveRegion.stageUpdate` | 階段 {n}{label}{status} | Stage {n}: {label}, {status} |
---
## 8. 資料模型(交給 Architect / Frontend 參考)
```typescript
type StageState =
| 'pending'
| 'running'
| 'running-slow' // UI 派生狀態running 且 elapsed > 20s
| 'done'
| 'failed'
| 'skipped';
interface Stage {
id: 1 | 2 | 3 | 4 | 5 | 6;
state: StageState;
startedAt: number | null; // epoch ms
finishedAt: number | null;
errorMessage?: string;
}
interface StartupProgressState {
currentStage: 1 | 2 | 3 | 4 | 5 | 6 | 'done' | 'error';
stages: Record<1 | 2 | 3 | 4 | 5 | 6, Stage>;
startedAt: number; // 整個啟動流程開始時間
totalElapsedMs: number; // 從 startedAt 算起
errorReason?: 'timeout' | 'stageFailed';
failedStageId?: number;
}
```
**訊號來源(給 Architect 看)**
- 階段 1 完成Wails `OnStartup` 回呼結束 + 面板 mount 事件
- 階段 2 完成Go `ensurePythonRuntime()` 回傳 + driver check OK
- 階段 3 完成:`/api/health` 首次回 200
- 階段 4 完成Go server 裝置掃描回傳(有 / 無 / 錯誤都算)
- 階段 5 完成:`OpenInBrowser()` 呼叫 return`skipped` 若 toggle 關)
- 階段 6 完成WebSocket hub 收到第一個 client 連線事件(**R5-E6**
每個階段的 `startedAt` 由前端 React 在「前一階段變 done 時」設定當下時間。`totalElapsedMs` 每秒更新用於超時判斷。
**60 秒總計時 timer**
- Panel mount 時 `setTimeout(fireError, 60_000)`
- 每個階段 `done` 時不清 timer
- 只要最後階段6`done` 前 timer 觸發 → 進 Error modereason: `timeout`
- 階段 6 `done``clearTimeout`
**20 秒階段卡頓 timer**
- 每個階段變 `running``setTimeout(markSlow, 20_000)`
- 階段變 `done``failed``clearTimeout`
- 觸發時 `stage.state` 不真的改成 `running-slow`(仍是 `running`),而是 UI 層根據 `now - startedAt > 20_000` 派生出 `running-slow` 樣式和 hint line
---
## 9. 開發備忘(交給 Frontend / Architect
1. **技術實作位置**:本面板跑在 Wails 控制台前端vanilla HTML/JS/CSS非 Next.js路徑約 `visiona-local/frontend/`
2. **狀態同步**Go 端每個階段完成時透過 `wailsRuntime.EventsEmit('startup:stage', {id, state})`;前端 listen 後 setState 並更新 panel
3. **Go 端需新增事件**
- `startup:stage` payload `{id: 1..6, state: 'running'|'done'|'failed'|'skipped', errorMessage?: string}`
- 最終 `startup:complete``startup:error` 作為 panel 淡出訊號
4. **階段 6 WebSocket 訊號**:需要在 Go WebSocket hub 的 `OnConnect` 事件中,若是**這個 process 生命週期的第一次 client 連線**emit `startup:stage {id: 6, state: 'done'}`;後續連線不再 emit
5. **階段 5 Linux 跳過時**Go 端若讀到 `Preferences.OpenBrowserOnStart == false`emit `startup:stage {id: 5, state: 'skipped'}`
6. **Architect 待補 TDD**:本面板需 TDD 對應章節(建議位於 `04-architecture/v2/startup-progress.md` 或併入 `control-panel.md`)落地事件協議 + Go 端 stage emitter 位置
---
## 10. 與 v2 Starting state 的差異對照
| 面向 | v2第一版 | v2.1 |
|------|-------------|------|
| Starting 視覺 | 只有 header 一顆 spinner + `Starting...` 文字 | 浮出 6 階段進度面板,每階段有 label + description + 狀態 icon |
| 時間上限 | 5 秒超時進 Errorv1 寫死) | **60 秒總上限**R5-E1任一階段 20 秒進 Retry hintR5-E3 |
| 使用者可見性 | 零(只有「啟動中」三字) | 每階段明確的中英雙語 label + description |
| Error state 入口 | 一律從 watchServer 失敗 | 新增「60 秒 timeout」與「階段失敗」兩條路徑都走 Error mode |
| 無障礙 | 基本 `aria-live` | 新增 `role="progressbar"` + `aria-valuenow` + live region 逐階段宣告 |
---
## 11. 懸而未決問題(交 Orchestrator
1. **20 秒 retry hint 的「正在重試」字面**:目前文案寫「正在重試...」retrying但實際上 Go 端不一定真的在 retry可能只是單純慢。是否改為較中性的「正在處理中請稍候... / Still working, please wait...」Design 建議維持「正在重試」—— 使用者對「retry」比「still working」更有信心感即使技術上沒在 retry心理預期是「系統知道有問題、在努力中」。待使用者最終確認。
2. **階段 6 WebSocket 連線訊號失敗的情境**:若 OS open browser 成功但使用者的預設瀏覽器被某些安全軟體攔截Windows Defender SmartScreen 或家長控制),階段 6 永遠等不到 WebSocket → 60 秒後進 Error mode。此時 Error 說明是否應該特別提示「請檢查瀏覽器是否被安全軟體攔截」Design 建議:不做特殊偵測,通用說明 + 看 log details 即可,否則要辨識各種安全軟體失敗 pattern過度設計。
3. **階段卡頓超過 20 秒仍未完成,使用者按 Retry 的語意**:是「重置整個啟動流程」(從階段 1 開始還是「重試當前階段」從當前階段重跑Design 建議:**重置整個啟動流程**簡單明確、使用者心智模型一致Go 端只需要暴露一個 `RestartStartupSequence()` API。待 Architect 確認可行。
---
**下一步**:交 Architect 審閱技術落地事件協議、WebSocket 訊號、Go 端 stage emitter 位置)、交 M8-5 Frontend Agent 實作 Wails 控制台啟動進度面板。