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

26 KiB
Raw Permalink Blame History

v2.6 — 啟動進度面板Startup Progress Panel

本章對應 R5-E1 ~ R5-E6perceived performance 階段化啟動)。 上層索引:../design-spec-v2.md 相關:v2/control-panel.md §5(狀態機 Starting statev2/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 msprefers-reduced-motion 時跳變)
收尾動畫 所有階段完成後 fade-out 200 ms之後 unmount

3.2 Panel header

元素 類型 樣式
標題 <h2> 14 px SemiBoldcolor.foreground
文字zh-TW 正在啟動 visionA-local
文字en Starting visionA-local

i18n keystartup.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> 完成 / 進行中 / 等待中 / 失敗 右對齊

範例結構

<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
隱藏 階段狀態變 donefailed 後隨 StageItem 一起消失
顏色 color.warning text
Icon ⚠ 14×14
雙語 第一行中文、第二行英文12 px muted

文案

  • zh這個步驟花的時間比預期久,正在重試...
  • enThis step is taking longer than expected, retrying...

i18n keystartup.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 toastcontrol-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 跳到 runninglabel description 改為
    • zh請點擊控制台的「在瀏覽器開啟」按鈕
    • enPlease 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 openedToggle 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}zhStage {n}: {label}, {status}en
Focus trap Panel 顯示期間不 trap focusStarting state 使用者不應該需要操作 panel 以外的元素,但允許使用者切換視窗或捲 log panel
Keyboard ⌘0 / Ctrl+0 focus 進度面板第一個可聚焦元素Retry 按鈕或 panel root
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 參考)

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() 呼叫 returnskipped 若 toggle 關)
  • 階段 6 完成WebSocket hub 收到第一個 client 連線事件(R5-E6

每個階段的 startedAt 由前端 React 在「前一階段變 done 時」設定當下時間。totalElapsedMs 每秒更新用於超時判斷。

60 秒總計時 timer

  • Panel mount 時 setTimeout(fireError, 60_000)
  • 每個階段 done 時不清 timer
  • 只要最後階段6done 前 timer 觸發 → 進 Error modereason: timeout
  • 階段 6 doneclearTimeout

20 秒階段卡頓 timer

  • 每個階段變 runningsetTimeout(markSlow, 20_000)
  • 階段變 donefailedclearTimeout
  • 觸發時 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:completestartup: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 == falseemit 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 控制台啟動進度面板。