依 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>
26 KiB
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 Hint,R5-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-E4:60 秒總上限或任一階段失敗)
│ ┌───────────────────────────────────────────────────────────────┐ │
│ │ ❌ 啟動失敗 · 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 Medium,muted-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 → running:spinner fade-in 150 ms
- running → done:spinner → check icon 交叉淡入 200 ms;整行 label 漸變淡
- running → running-slow:⚠ icon slide-in-left 200 ms
- running → failed:spinner → ❌ 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 Hint(R5-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 State(R5-E4)
任一階段 failed 或總計超過 60 秒 → Panel 整體換為 Error mode:
- StageItem 列表隱藏(只保留失敗的那一階段顯示為 ❌)
- 進度條換成 Error 樣式(整條
color.destructive/20背景) - 大標題
啟動失敗 / Startup failed - 說明文字(雙語)
- 三顆按鈕:
| 按鈕 | 類型 | 行為 |
|---|---|---|
| 🔄 重試 / Retry | Button primary md |
重置進度面板,重新跑階段 1 |
| 📋 檢視 log / View Log | Button ghost md |
收起 panel,focus 到 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
- zh:
- 當使用者手動點 Open in Browser 並成功建立 WebSocket 連線後 →
done - 不套 20 秒 retry hint(因為是等待人為動作,不是系統卡住)
5. 成功狀態收尾(Running state 轉場)
當階段 6 狀態變 done:
- 進度條最後一格填滿
color.success,脈衝動畫停止 - 停留 500 ms 讓使用者看到「全綠」
- 整個 Panel fade-out 200 ms
- Panel unmount
- 控制台 Status 區域變
Running · Browser opened(Toggle ON 首次)或純Running - Primary controls 啟用(Open in Browser 等)
總轉場時間:約 700 ms(500 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)Esc:若 panel 已進入 Error state → 不動作(避免誤關);若進度已跑完正在 fade-out → 立即 unmount |
| 色彩對比 | Stage label / Description / 狀態文字 ≥ 4.5:1;failed / running-slow hint ≥ 4.5:1(critical 信號不妥協,對齊 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()呼叫 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 mode(reason: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)
- 技術實作位置:本面板跑在 Wails 控制台前端(vanilla HTML/JS/CSS,非 Next.js),路徑約
visiona-local/frontend/下 - 狀態同步:Go 端每個階段完成時透過
wailsRuntime.EventsEmit('startup:stage', {id, state});前端 listen 後 setState 並更新 panel - Go 端需新增事件:
startup:stagepayload{id: 1..6, state: 'running'|'done'|'failed'|'skipped', errorMessage?: string}- 最終
startup:complete或startup:error作為 panel 淡出訊號
- 階段 6 WebSocket 訊號:需要在 Go WebSocket hub 的
OnConnect事件中,若是這個 process 生命週期的第一次 client 連線,emitstartup:stage {id: 6, state: 'done'};後續連線不再 emit - 階段 5 Linux 跳過時:Go 端若讀到
Preferences.OpenBrowserOnStart == false,emitstartup:stage {id: 5, state: 'skipped'} - 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 秒超時進 Error(v1 寫死) | 60 秒總上限(R5-E1),任一階段 20 秒進 Retry hint(R5-E3) |
| 使用者可見性 | 零(只有「啟動中」三字) | 每階段明確的中英雙語 label + description |
| Error state 入口 | 一律從 watchServer 失敗 | 新增「60 秒 timeout」與「階段失敗」兩條路徑,都走 Error mode |
| 無障礙 | 基本 aria-live |
新增 role="progressbar" + aria-valuenow + live region 逐階段宣告 |
11. 懸而未決問題(交 Orchestrator)
- 20 秒 retry hint 的「正在重試」字面:目前文案寫「正在重試...」(retrying),但實際上 Go 端不一定真的在 retry,可能只是單純慢。是否改為較中性的「正在處理中,請稍候... / Still working, please wait...」?Design 建議維持「正在重試」—— 使用者對「retry」比「still working」更有信心感,即使技術上沒在 retry,心理預期是「系統知道有問題、在努力中」。待使用者最終確認。
- 階段 6 WebSocket 連線訊號失敗的情境:若 OS open browser 成功但使用者的預設瀏覽器被某些安全軟體攔截(Windows Defender SmartScreen 或家長控制),階段 6 永遠等不到 WebSocket → 60 秒後進 Error mode。此時 Error 說明是否應該特別提示「請檢查瀏覽器是否被安全軟體攔截」?Design 建議:不做特殊偵測,通用說明 + 看 log details 即可,否則要辨識各種安全軟體失敗 pattern,過度設計。
- 階段卡頓超過 20 秒仍未完成,使用者按 Retry 的語意:是「重置整個啟動流程」(從階段 1 開始)還是「重試當前階段」(從當前階段重跑)?Design 建議:重置整個啟動流程(簡單明確、使用者心智模型一致),Go 端只需要暴露一個
RestartStartupSequence()API。待 Architect 確認可行。
下一步:交 Architect 審閱技術落地(事件協議、WebSocket 訊號、Go 端 stage emitter 位置)、交 M8-5 Frontend Agent 實作 Wails 控制台啟動進度面板。