visionA/local-tool/.autoflow/03-design/v2/server-offline-overlay.md
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

13 KiB
Raw Blame History

v2.2 — Server Offline Overlay 設計規格

本章對應 R5-2關閉 Wails 視窗 = 結束 server瀏覽器端顯示離線 Overlay。 上層索引:../design-spec-v2.md


1. 背景與目的

R5-2 決定關閉 Wails 控制台 = 結束 server,但瀏覽器端不知道這件事發生。如果使用者一手把控制台關掉、另一手還停留在瀏覽器 tab

  • 下一次發 fetch → 連線被拒(ECONNREFUSED
  • 正在 stream 的 MJPEG / WebSocket → 突然斷線
  • 使用者體感:「我只是關掉那個 log 視窗,為什麼網頁壞了?」

Server Offline Overlay 是當瀏覽器端偵測到 server 不可達時,全螢幕蓋住整個 Web UI的硬阻斷畫面明確告訴使用者「server 真的沒了」,並提供重試 / 重開 app 的自助路徑。


2. 視覺 Wireframe

╔═══════════════════════════════════════════════════════════════════╗
║                                                                     ║
║                                                                     ║
║                                                                     ║
║                           ┌─────────┐                              ║
║                           │   ⚠     │                              ║
║                           │         │                              ║
║                           └─────────┘                              ║
║                                                                     ║
║                     Local Server 已離線                             ║
║                                                                     ║
║           visionA-local 已結束或崩潰,請重新開啟應用程式            ║
║                                                                     ║
║                                                                     ║
║                  ┌────────────────────────┐                         ║
║                  │      重試連線          │                         ║
║                  └────────────────────────┘                         ║
║                                                                     ║
║                        了解更多 ↓                                   ║
║                                                                     ║
║                                                                     ║
║      ┌──────────────────────────────────────────────────┐          ║
║      │ 如何重新啟動 visionA-local                      │          ║
║      │                                                   │          ║
║      │ 1. 前往應用程式資料夾或 Dock                      │          ║
║      │ 2. 雙擊 visionA-local 圖示                        │          ║
║      │ 3. 控制台會自動啟動伺服器並重新開啟瀏覽器         │          ║
║      │                                                   │          ║
║      │ 如果問題持續發生,請檢查 Log 或回報問題           │          ║
║      └──────────────────────────────────────────────────┘          ║
║                                                                     ║
║                                                                     ║
╚═══════════════════════════════════════════════════════════════════╝
 ↑ 半透明黑色背景覆蓋整個 viewport底下原 Web UI 仍隱約可見但不可互動

2.1 尺寸與位置

元素 規格
Overlay 背景 position: fixed; inset: 0; z-index: 9999
背景色Light rgba(0, 0, 0, 0.55) + backdrop-filter: blur(8px)
背景色Dark rgba(0, 0, 0, 0.72) + backdrop-filter: blur(8px)
內容卡片寬度 clamp(320px, 42vw, 480px)
內容卡片背景 color.surfaceLight近白Dark近黑
內容卡片圓角 radius.xl16 px
內容卡片 padding 40 px
內容卡片 shadow shadow.xlv1 token

2.2 元件規格

元素 類型 規格 文字
Icon 容器 <div> 80×80圓形 color.destructive/10 背景
Icon <svg> 警告三角 40×40color.destructive
標題 <h1> 24 px Bold color.foreground 置中 Local Server 已離線
副標 <p> 15 px Regular color.muted-foreground 置中 visionA-local 已結束或崩潰,請重新開啟應用程式
重試按鈕 Button primary lg 寬度 240 px 置中 重試連線
了解更多 Button ghost sm toggle 展開下方 help text 了解更多 ↓ / 收起 ↑
Help text 區 <div> 展開 padding 16 inline-block背景 color.muted/30,圓角 radius.md 見 §5

2.3 Dark Mode

  • 背景半透明更深(rgba(0,0,0,0.72)
  • 卡片背景改為 color.surface-2Dark 模式下比 surface 淺一階,視覺上「浮起」)
  • Icon 圓圈背景改為 color.destructive/15
  • 所有文字對比保持 ≥ 4.5:1

3. 觸發條件

3.1 首次載入

使用者首次在瀏覽器打開 http://127.0.0.1:{port}/ 時:

頁面載入
  ↓
前端 boot sequence: fetch GET /api/health
  ↓
  ├── 2xx → 正常顯示 Web UI不顯示 Overlay
  └── 網路錯誤 / 非 2xx → 立即顯示 Overlay

3.2 使用中偵測

當瀏覽器 Web UI 已經在跑時,透過以下兩個管道偵測 server 斷線:

3.2.1 定期 Health Check主動

  • Polling /api/health,間隔 10 秒
  • 連續 2 次 失敗20 秒)→ 顯示 Overlay
  • 為什麼是 2 次而非 3 次Local 127.0.0.1 幾乎不可能有網路抖動2 次已足夠避免誤報3 次會拖到 30 秒,使用者早就察覺異常

3.2.2 WebSocket / SSE 斷線(被動)

  • 現有的 camera-store 使用 SSE / WebSocket 接收 inference 結果
  • 任何 onclose / onerror event → 立即 觸發一次 /api/health 驗證
  • 如果 health check 也失敗 → 直接顯示 Overlay不等 10 秒 polling
  • 如果 health check 成功(代表只是單一 stream 斷線)→ 由 camera-store 自己處理 reconnect不顯示 Overlay

3.3 首次載入特殊情境

如果使用者把瀏覽器書籤設成 http://127.0.0.1:3721/workspace/xxx,但 visionA-local 還沒啟動 → 首次 fetch 就會失敗 → Overlay 立即顯示。這個情境比「中途斷線」更常見,必須處理。


4. Dismiss 條件

條件 行為
按「重試連線」按鈕 立即呼叫一次 /api/health,成功 → 自動 dismiss失敗 → 按鈕顯示 loading 500 ms然後 shake 動畫 + toast「仍無法連線請檢查 visionA-local 是否執行中」
背景 polling 成功 自動 dismiss(覆蓋層淡出 200 ms
使用者手動 reload 頁面 頁面重載 → 首次載入流程 → 若 server 仍掛Overlay 再次出現
使用者按 ESC / 點背景 不 dismiss(這是硬阻斷,不允許偷偷略過)

4.1 自動重連的細節

Overlay 顯示期間,仍維持 polling /api/health(間隔 3 秒,比正常時更積極)。這是為了使用者雙擊 app 重開 → 等 3-6 秒 → 不需手動點重試就自動恢復,接近 Ollama 「server 回來就立刻恢復」的體驗。


5. 文案(雙語完整版)

5.1 主文案

位置 zh-TW en
標題 Local Server 已離線 Local Server is offline
副標 visionA-local 已結束或崩潰,請重新開啟應用程式 visionA-local has stopped or crashed. Please restart the app.
Primary CTA 重試連線 Retry connection
Secondary CTA展開前 了解更多 ↓ Learn more ↓
Secondary CTA展開後 收起 ↑ Hide ↑
Retry 失敗 toast 仍無法連線,請檢查 visionA-local 是否執行中 Still cannot connect. Please check if visionA-local is running.

5.2 展開的 Help text繁中

如何重新啟動 visionA-local

1. 前往應用程式資料夾或 Dock
2. 雙擊 visionA-local 圖示
3. 控制台會自動啟動伺服器並重新開啟瀏覽器

如果問題持續發生,請檢查 Log 或回報問題。

5.3 展開的 Help text英文

How to restart visionA-local:

1. Go to your Applications folder or Dock
2. Double-click the visionA-local icon
3. The control panel will automatically start the server and reopen the browser

If the issue persists, please check the logs or report the problem.

6. 動畫與 Transition

事件 動畫 時長
Overlay 顯示 背景 opacity 0 → 1 + backdrop-filter blur(0) → blur(8);卡片 opacity 0 → 1 + translateY(20px → 0) 250 ms ease-out
Overlay 消失 反向 200 ms ease-in
重試按鈕 click Button scale 0.981 150 ms
重試失敗 shake 卡片 translateX ±4px 3 次 400 ms
Help text 展開 / 收起 height auto transition + opacity 200 ms ease-out
prefers-reduced-motion 全部動畫降為 0 ms 跳變

7. 與 Toast 系統的差異

面向 Toast Server Offline Overlay
呈現 螢幕上方 / 下方滑入的小卡片 全螢幕半透明覆蓋層
可關閉 可(點 ✕ 或自動 3-5 秒消失) 不可關閉(只能靠重試成功 / reload
可互動底層 UI Toast 不阻擋) 不可(底層全部 pointer-events: none
用途 成功 / 失敗的短暫通知 結構性錯誤,使用者必須知道且必須行動
觸發頻率 極低(只有 server 斷線)

設計原則Server 斷線代表「整個應用程式沒了」這不是「提示」等級是「必須正視」等級。Toast 無法傳達嚴重程度。Overlay 是唯一合理的呈現方式。


8. 無障礙考量

項目 設計
ARIA role <div role="alertdialog" aria-modal="true" aria-labelledby="offline-title" aria-describedby="offline-desc">
Focus trap Overlay 顯示時焦點強制落在「重試連線」按鈕上Tab 只能在重試 / 了解更多 兩顆按鈕之間循環
ESC 鍵 不 dismiss(硬阻斷),但不阻止 ESC避免破壞 browser 原生 ESC 行為,例如退出全螢幕)
Screen reader Overlay 顯示瞬間 announce 標題 + 副標(aria-live="assertive"
色彩對比 標題、副標、按鈕文字全部 ≥ 4.5:1在半透明背景上也要達標因此卡片背景必須是純色不是半透明
鍵盤可達 只有兩個 interactive 元素(重試、了解更多),都可 Tab 聚焦

9. 實作注意事項(給 Frontend

9.1 放置位置

建議在 frontend/src/app/layout.tsx 掛一個全域 Provider

<ServerHealthProvider>
  <ServerOfflineOverlay />
  {children}
</ServerHealthProvider>

ServerHealthProvider 負責:

  • 啟動 10 秒 interval polling /api/health
  • 暴露 useServerHealth() hook 給任何需要知道 server 狀態的元件
  • 監聽 SSE / WebSocket 錯誤事件

ServerOfflineOverlay 訂閱 hook狀態變 offline 時 renderonline 時 unmount。

9.2 不要在 First-Run 前就觸發 Overlay 的誤報

使用者首次載入網頁時First-Run wizard 還沒跑。如果 health check 剛好在 fetch 其他 /api/... 之前觸發,而那些 API 需要 DB 初始化可能出現health check 成功、但其他 API 還沒 ready。

解法Overlay 只依賴 /api/health 的結果,不依賴其他 API。/api/health 在 server 啟動後立即 200不等任何 DB init。

9.3 Restart Server 場景的特殊處理

雖然 R5-2 選了「關閉視窗 = 結束 server」但 R5 三方共識仍保留「Restart Server」按鈕控制台 Manage menu。當使用者按 Restart 時:

  1. Server 短暫 StoppingStartingRunning,中間可能有 2-5 秒斷線
  2. 瀏覽器偵測到 health check 失敗 → Overlay 顯示
  3. 3 秒後 polling 發現 server 回來 → Overlay 自動消失
  4. 使用者體感:短暫看到 Overlay → 自動恢復 → 繼續用

這個行為符合預期不需要特別處理。Overlay 對「暫時斷線」和「永久斷線」的區別透過 polling 自動恢復 vs 使用者手動重開 app自然分化。


10. 與 v1 的差異

面向 v1 v2
Server 斷線 UX 未定義 全螢幕 Overlay新增
使用者教育 展開式 Help text教重開
自動恢復 3 秒 pollingserver 回來自動 dismiss
文案 中英雙語完整版

下一步:交 Frontend Agent 實作 ServerHealthProvider + ServerOfflineOverlay 元件。