visionA/local-tool/.autoflow/03-design/design-analysis-round2-refactor.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

538 lines
37 KiB
Markdown
Raw 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.

# Design 第一輪分析 — visionA-local 方向變更2026-04-14
> Design Agent · 第一輪分析筆記(非正式規格) · 2026-04-14
> 對應事件:使用者提出方向重大變更 — Wails 桌面 app 由「跑 Next.js 主 UI 的殼」**退化**為「Local Server 控制台」,主 UI 改為純瀏覽器網頁。
---
## 摘要3 行)
- 使用者要求把 visionA-local 從「Photo Booth 型桌面 app」轉為「**Docker Desktop / Ollama 型服務控制台 + 瀏覽器網頁 UI**」雙 UI 架構桌面殼只管「server 生命週期 + log」瀏覽器端承擔所有業務操作。
- 推論輸入從目前的 `camera / image / video(file) / video(url)` **砍為三種**`camera / image / video 檔案上傳`**URL/YT-DLP 整條移除**;模型來源只剩「預置 + 使用者上傳」,不再有第三方目錄。
- 這個改動在體驗面有實質 upside多視窗、devtools、書籤、和 localhost 生態整合),但也讓原本已完成的 splash regression 修復方向逆轉、和第三輪 Q-A「砍 tray」決策再度打架因為沒有 tray 又沒有主視窗、server 要不要在桌面 app 關閉後繼續活)。**這幾個衝突必須在進開發前先解。**
---
## A. 變更解讀(體驗面)
### A1. 為什麼使用者想這樣
使用者沒明講動機,但以 UX 研究角度推測有幾個合理動機,**按可能性由高到低**排序:
1. **對齊 localhost 生態的心智模型**
使用者自己是 FAE / 開發者,日常工具鏈是 `docker desktop` / `ollama serve` / `jupyter notebook` / `stable-diffusion-webui`。這些工具都走「桌面端只是 server 控制器,真正操作在瀏覽器 / CLI」的雙重介面模式。把 visionA-local 做成同樣形狀,學習成本 = 0符合「這類工具應該長這樣」的直覺。
2. **瀏覽器能力是 Wails WebView 的超集**
- **多視窗**:同時開兩個 tab 對照 camera 推論 vs 影片推論
- **devtools**F12 即用,可以看 network、console、storage
- **書籤 / 分頁歷史**:跳回上次看的裝置
- **瀏覽器擴充**screenshot、錄影、Postman 等 workflow 工具直接接上
- **瀏覽器字體 / 高 DPI / 縮放**Wails 在 Windows 的 WebView2 某些版本字體渲染有 bug瀏覽器沒這問題
- **分享 URL**`http://localhost:3721/workspace/xxx` 可以貼給同事或記在筆記裡
3. **推論結果可以在不同視窗同時看**
目前 Wails 單視窗模式,使用者只能看一個 workspace。FAE 到客戶現場 demo 時常見需求:一邊跑 camera、一邊準備下一段影片 — 開兩個瀏覽器 tab 就解決了。
4. **切換到不同 OS 或遠端機時維持一致**
雖然 visionA-local 定位單機,但使用者也許會在 Linux 無頭機器上 run server然後用 Mac 筆電瀏覽器連 LAN 過去 demo。**這個動機如果存在,就不是單純的 UX 偏好,而是產品範圍的擴張**LAN 可訪問),要主動問清楚(見 D 節)。
5. **Server 健康度獨立可見**
目前 Wails UI 把 server 當成黑盒子server 崩了 UI 才喊 fatal dialog。獨立 log 面板讓使用者**在事情出錯前**就看到徵兆(例如 Python sidecar 有 warning降低 debug 門檻。
**最有說服力的論證是 1 + 5 的組合**:使用者想要的不是「瀏覽器介面」這個手段,而是「**把 visionA-local 從『桌面軟體』重新定位為『本機服務 + 網頁控制台』**」這個心智模型。這是產品層面的 re-positioning不只是 UI 重排。
### A2. 對使用者旅程的影響
#### 首次開啟流程First-Run
**現況M7 splash + Next.js**
```
雙擊 app → Wails 視窗開 → splash 輪詢 GetServerStatus
→ server 起來 → window.location.replace 跳 Next.js 主 UI
→ First-Run 歡迎頁(歡迎 / 模式 / 偵測)→ Dashboard
```
**新方向**
```
雙擊 app → 桌面控制台視窗開 → 自動 start server → log 跑 → status: running
→ 使用者點「Open in browser」→ 瀏覽器開 localhost:3721 → 進 First-Run → Dashboard
```
**體驗斷裂點**
- 使用者從「雙擊 app → 看到 app」變成「雙擊 app → 看到 server 控制台(不是我要的)→ 還要再點一下」。這是**多一個步驟**。
- 對第一次用的 FAE 來說看到「Local server: running · Open in browser」可能會愣一下 — 「我不是要裝網頁工具啊?」
- **解法**:首次啟動**自動**彈出瀏覽器桌面控制台照樣開著在背景。Docker Desktop 沒這麼做是因為 docker daemon 不需要瀏覽器,但 Ollama / Stable Diffusion WebUI 都會自動開。對齊後者體驗。
#### 日常使用流程
**現況**:雙擊 app → 直接操作
**新方向**:雙擊 app → 瀏覽器自動彈(或從 dock/tray 記憶上次狀態)→ 操作
- 如果使用者前一個 session 還沒關瀏覽器 tab雙擊 app 時不該又彈一個新 tab — 應該偵測既有 tab 並 focus技術上 `window.open` with same `target` name 就行)。
- 如果桌面控制台已經在跑、使用者雙擊 app 第二次,要 raise 既有控制台視窗single-instance第四輪決策有定義 `/ipc/raise` endpoint可重用
#### 錯誤排除流程
**這是新架構最大的 UX 升級點。** 目前 server 崩潰時,使用者看到的是:
- Wails 視窗從 Next.js UI 變回 splash
- 或者彈出 fatal dialog「Server 已停止」
- 使用者要自己去 `~/Library/Application Support/visiona-local/logs/` 翻 log
**新架構**
- 桌面控制台永遠看得到 logserver 崩了會有紅色行 / stack trace 直接 inline
- 一鍵 Restart 按鈕就地重試
- 可以 Copy log 貼 slack 問人
- 「Open logs folder」按鈕開 Finder/Explorer 到 log 目錄
**這個價值是 A1 論證 5 的具體落地。桌面控制台不是退化,是獲得一個「自助除錯介面」。**
#### 離開流程
**第四輪決策 Q7 是「B 傳統式(關閉 = 結束)」。新架構打到這個決策頭上:**
- 如果桌面控制台關了 = server 停了 → 瀏覽器的網頁就 **突然變磚塊**fetch 全 500camera 串流斷線)。使用者會很困惑:「我只是關掉那個 log 視窗啊?為什麼我的推論頁面壞了?」
- 如果桌面控制台關了 ≠ server 停 → 又回到 tray / 背景服務心智模型,和第三輪 Q-A「砍 tray」決策衝突。
- **這是 D 節最重要的待決策問題**。
### A3. 現有頁面搬遷工作
現有 Next.js 頁面結構:
```
frontend/src/app/
├── layout.tsx ← 全域 layoutsidebar + header
├── page.tsx ← Dashboard/
├── workspace/[deviceId] ← 推論主畫面
├── devices/ ← 裝置列表
├── devices/[id] ← 裝置細節
├── models/ ← 模型列表
├── models/[id] ← 模型細節
└── settings/ ← 設定4 分頁)
```
**搬遷需要改動的點**
| 面向 | 現況 | 新架構需要做什麼 | 工作量 |
|------|------|-----------------|--------|
| **頁面結構** | Next.js pages | **完全不用動**。現有頁面直接以瀏覽器開 `http://localhost:<port>/` 就能跑 | 0 |
| **workspace 推論源 tabs** | camera / image / videovideo tab 內有 file/url 雙模式 | **砍掉 video tab 內的 url 按鈕**video tab 變成單一「upload file」。`pasteUrl` / `urlPlaceholder` / `urlHelpText` i18n keys 刪除。`source-selector.tsx``videoMode` state 和 `startFromUrl` 呼叫都刪。另外 `batch_image` sub-mode 保留(它是 image tab 的多檔上傳變體)。 | S |
| **第四輪「url 推論首次 ≤ 250ms」指標** | 本來假設 url 來源也算在延遲指標內 | URL 整條砍後,這條指標只剩 camera + 本地 video file。更新 PRD 的 R4-2 註記。 | XS文字調整 |
| **模型來源** | models 頁面有預置模型 + Upload Dialog | 使用者說「除了預設的幾種只能用上傳的」→ 確認這和目前狀態一致(預置 + 上傳),**不需要改**。只是要刪掉任何「從 URL 下載模型」或「線上模型市集」的殘留(目前沒有,但要檢查) | XS |
| **devices 頁面 scan/connect** | 現有已經有 `Refresh Devices` 按鈕(快捷鍵 ⌘Shift+R也有 per-device connect | 不用改使用者說的「scan/connect device 介面」已經存在 | 0 |
| **Wails 原生呼叫** | splash 用 `GetServerStatus()` binding、fatal dialog 用 `runtime.EventsEmit` | **splash 從瀏覽器消失**變成桌面控制台內部邏輯。Next.js 主 UI 必須**徹底不用任何 Wails JS binding**M7-B 設計時已經做到,這點不用改)| 0 |
| **Dark Mode 偵測** | 現況走 CSS `prefers-color-scheme`(第四輪決策) | 瀏覽器原生支援,不用改 | 0 |
| **i18n 切換** | Settings > 一般 > 語言 | 不用改,瀏覽器 localStorage 持久化即可 | 0 |
| **OS 通知** | 第四輪 R4-8裝置連/斷 toast、server 崩潰 native notification | **toast 完全不變**(瀏覽器支援好)。**Server 崩潰的 native notification 換位置**:不再由 Wails 處理,改由**桌面控制台自己處理**(它本來就是服務管理者,由它發通知比瀏覽器更合邏輯) | S |
| **Sidebar 與 Top bar** | Next.js 的 `layout.tsx` 已實作,含 sidebar navigation + user-friendly chrome | 不用改,但要考慮**要不要隱藏任何「return to desktop app」按鈕**之類的東西(目前沒有) | 0 |
| **Window chrome tokens** | 第四輪 `spec/07-design-tokens.md §7.5` 有 desktop 專用 elevation / window chrome token | 這些 token 只在桌面控制台用;網頁端不用(瀏覽器 chrome 由瀏覽器繪製)。不需要刪,但桌面控制台可以沿用 | 0 |
| **快捷鍵集**⌘1-4 切換主區塊、⌘,、⌘U 等) | 現有由 Next.js 內部 keyboard handler 綁定 | **分裂為兩套**:桌面控制台只有 `⌘W關閉結束/ ⌘Q`;其餘 ⌘1-4、⌘, 、⌘Shift+R、⌘U 全部留在瀏覽器端。`⌘W` 在瀏覽器是「關 tab」這是原生行為不衝突。**但 `⌘Q` 在瀏覽器是「結束 browser」會誤殺所有 tab**,要和使用者確認怎麼處理(見 D 節) | M |
**總結****現有 Next.js 頁面 80-90% 可以零改動直接搬**。實際修改集中在三個點:
1. 砍 workspace 的 URL 推論 UI1 個元件 + 相關 i18n
2. 桌面控制台是全新 appWails main.go 大改)
3. First-Run 自動開瀏覽器的新 hook
### A4. 砍掉 URL 推論的影響
使用者這句「推論只需包含這三種 camera/image/上傳影片」等於宣判 URL 推論死刑,影響面:
**UX 面影響**
1. **workspace 的 video tab 簡化**
- 現況:點 video tab → 看到 `[上傳檔案] [貼上連結]` 兩顆 button → 選 file → 再點 Select Video
- 新方向:點 video tab → **直接就是檔案上傳區**(大 drop zone + 檔案選擇)
- 這**改善**了 video tab 的體驗(少一個決策層),符合 Jakob Nielsen 的「減少選擇負擔」原則。
- 支援格式:使用者說 `avi, mpeg, mp4, 瀏覽器能吃的格式`,實際上瀏覽器 `<input type="file" accept="video/*">` 就能列一批。我建議 accept list 顯式寫 `.mp4,.avi,.mpeg,.mpg,.mov,.mkv,.webm`(瀏覽器相容格式的超集),因為後端是 ffmpeg 而非 `<video>` tagffmpeg 吃得下的更多。**這條要和 Architect 確認 ffmpeg 的 decoder whitelist**。
2. **i18n 文案刪除**
`camera.pasteUrl` / `camera.urlPlaceholder` / `camera.urlHelpText` 三個 key 刪掉zh-TW + en避免殘留 dead key。
3. **yt-dlp 打包怎麼辦?**
- 第四輪 R4 已決定 yt-dlp 要內嵌35MB+ ffmpeg 內嵌77MB GPL build。URL 推論砍掉後,**yt-dlp 的 35MB 完全是死重量**。
- **強烈建議**把 yt-dlp 從 vendor 清單移除,安裝檔可以瘦身 35MB220MB → 185MB
- **ffmpeg 不能砍**:本地影片解碼還是需要 ffmpeg`avi / mpeg / mov` 瀏覽器原生 `<video>` 不一定吃,後端 ffmpeg 是 decoder + transcode 關鍵)。
- **這條要寫進 D 節給 PM / Architect**:這是 M6 任務成果的部分退場,要追蹤 vendor 清單、Makefile、PRD 的第三方授權頁、安裝檔預期大小等多處更新。
4. **URL 推論是否有隱藏的未來用途?**
例如「RTSP 串流」IP camera也是走 URL 輸入。如果使用者未來想接監視器場景RTSP 就回不來了。**這條要和 PM 確認是否真的永久砍,還是只是 MVP 先砍**。(我的 PM hat 建議MVP 先砍future backlog 標記 `RTSP 可能回歸`。)
5. **對現場 demo 的影響**
FAE 帶筆電到客戶現場,如果沒有 Wi-Fi / 網路受限YouTube URL 本來就跑不動 — 砍掉它和「完全離線」承諾更一致。**這是 PM/PRD 的 strategy 論證可以直接套用的 UX narrative**。
**總結**:砍 URL 是**淨收益**(簡化 UI + 瘦身 + 對齊離線承諾),只要注意 yt-dlp 要一起砍、PRD 文案要清。
---
## B. 新的 Desktop Control Panel 設計提案
### B1. 視窗規格
| 項目 | 建議值 | 說明 |
|------|--------|------|
| 預設視窗寬度 | **720 px** | 不能太窄log 行要能看;不能太寬,沒必要 |
| 預設視窗高度 | **560 px** | log 區塊要能看 15-20 行 |
| 最小寬度 | **560 px** | 保證按鈕 + status 不疊 |
| 最小高度 | **420 px** | |
| 可調整大小 | 是 | 使用者可能想把它拖大看更多 log |
| 最大化按鈕 | 保留 | |
| 最小化按鈕 | 保留 | |
| 置頂 | 否 | 不干擾瀏覽器操作 |
| 全螢幕 | 否(不需要) | |
| Title bar | 原生 | 不做自訂 title bar省跨平台工 |
| 視窗標題 | `visionA-local · Server Control` | |
| 應用程式 icon | 沿用現有 `visiona-local/frontend/icon.png` | |
### B2. 佈局區塊ASCII wireframe
```
┌─────────────────────────────────────────────────────────────────┐
│ ● visionA-local · Server Control [─][□][✕]│ ← Title bar原生
├─────────────────────────────────────────────────────────────────┤
│ │
│ [visionA-local] Status: ●Running Port: 3721 v0.1.0 │ ← Header品牌 + 狀態列)
│ Uptime: 00:12:43 PID: 45821 │
│ │
├─────────────────────────────────────────────────────────────────┤
│ │
│ [ Open in Browser ] [ Start ] [ Stop ] [ Restart ] │ ← Primary controls
│ │
│ ☑ Follow tail ☑ Show timestamps [ Filter: _______ ] │ ← Log controls
│ │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 2026-04-14 10:23:41 INFO HTTP server listening on :3721 │
│ 2026-04-14 10:23:41 INFO wails ipc port: 49152 │
│ 2026-04-14 10:23:42 INFO device scan: found 1 Kneron KL520 │
│ 2026-04-14 10:23:43 INFO GET /api/devices 200 (4ms) │ ← Log panel
│ 2026-04-14 10:23:45 INFO GET /api/models 200 (2ms) │ (等寬字體, 可捲動,
│ 2026-04-14 10:23:58 WARN python sidecar restart (attempt 1) │ 等級著色)
│ 2026-04-14 10:23:59 INFO python sidecar ready │
│ 2026-04-14 10:24:12 INFO inference session start: classification│
│ ... │
│ │
│ │
│ │
├─────────────────────────────────────────────────────────────────┤
│ [ Clear log ] [ Copy log ] [ Export log ] [ Open log folder ] │ ← Log actions
│ │
│ Lines: 142 / 1000│ ← Footer status
└─────────────────────────────────────────────────────────────────┘
```
### B3. 元件細節
#### Header
- **品牌標記**:左側一個 20px 高的 visionA-local logo + 產品字樣(同現有 splash 視覺)
- **狀態指示燈**:大圓點 + 文字
- `●Running`(綠 / semantic `color.success`
- `●Starting...`(黃 / semantic `color.warning`,附 spinner
- `●Stopped`(灰 / semantic `color.muted-foreground`
- `●Crashed`(紅 / semantic `color.destructive`,附小 ⚠️ icon
- **運行資訊**port / uptime / PID / version — 次要資訊用 `color.muted-foreground`
- **沿用既有 design tokens**shadcn oklch不另外造一套
#### Primary Controls3 顆服務按鈕 + 1 顆瀏覽器按鈕)
| 按鈕 | 狀態邏輯 | 視覺優先級 |
|------|----------|----------|
| **Open in Browser** | 只在 `Running` 時 enabled點擊呼叫 OS open URL | **Primaryfilled**,位於最左側最顯眼處 |
| Start | 只在 `Stopped / Crashed` 時 enabled | Secondaryoutlined |
| Stop | 只在 `Running` 時 enabled點擊後要有確認避免誤按 | Secondary |
| Restart | 只在 `Running` 時 enabled | Secondary |
設計 rationale**`Open in Browser` 是日常最高頻操作**每次啟動都要按一次必須視覺最重Start/Stop/Restart 只在出事時才點。這違反了「Start 應該在第一個」的慣例,但符合使用頻率。
可以考慮**合併 Stop + Restart 為一顆「⋯」overflow menu**,避免誤按:
```
[ Open in Browser ] [ Start ] [ ⋯ Manage ▼ ]
├─ Stop server
├─ Restart server
└─ Clear data (dangerous)
```
#### Log 控制列Log controls
- **Follow tail**:預設開。使用者往上捲動時**自動關閉**(像 Docker Desktop / `tail -f`),捲到最底時自動重新開啟(或顯示 `Jump to latest` 按鈕)
- **Show timestamps**:預設開。關閉後 log 行不含時間戳,節省寬度
- **Filter**:即時過濾(字串 match不做 regex 避免使用者不會用。keyboard`⌘F` 聚焦 filter
#### Log panel
- **字體**等寬SF Mono / Consolas / Monospace
- **字級**12px無 line-height 壓縮
- **等級著色**
- `INFO` → 預設文字色
- `WARN``color.warning`(琥珀)
- `ERROR``color.destructive`(紅)
- `DEBUG``color.muted-foreground`
- **長度上限**:記憶體最多 1000 行ring buffer超過舊的丟。避免長時間跑爆記憶體。
- **完整歷史**:寫檔到 `~/Library/Application Support/visiona-local/logs/server.log`,和當前 `stderr.log` / `stdout.log` 共存Architect 確認)
- **支援選取複製**:使用者可以滑鼠拖選文字。無論 follow tail 是否開啟,選取時都要凍結畫面
- **Auto scroll 平滑**:新行滑入動畫 60ms太快會閃
- **Drag-select + ⌘A select all + ⌘C copy**:原生支援即可
#### Log actions底部按鈕列
| 按鈕 | 功能 |
|------|------|
| Clear log | 清空**畫面上**的 log不動 log 檔)。二次確認 |
| Copy log | 複製全部 log 到剪貼簿(連同 timestamp |
| Export log | 匯出 .log 檔到指定位置(原生 Save Dialog |
| Open log folder | 用 OS 預設 file manager 開 `~/Library/Application Support/visiona-local/logs/` |
#### Footer status
- **行數統計**`Lines: 142 / 1000`(顯示當前行數 / 上限)
- **Memory hint**(可選):`Server RAM: 240 MB` — 從 `ps` 取樣每 3 秒刷新,供使用者觀察是否記憶體漏水
### B4. 首次啟動行為
**建議預設行為**
1. 雙擊 app → 桌面控制台視窗開(預設位置:螢幕中央)
2. **自動** start server不需要使用者點 Start
3. Server 起來後status → Running**自動**呼叫 OS 開預設瀏覽器到 `http://localhost:3721/`
4. 桌面控制台**留在背景**(不最小化、不關閉 — 不然使用者會以為 app 壞了)
5. 使用者下次啟動時,記住**上次**的行為:
- 如果上次使用者**手動把控制台關閉**,下次啟動仍然彈控制台(因為關閉 = 結束程式,第四輪 Q7 決策)
- 如果上次使用者**只是把瀏覽器關了**,下次只要控制台還活著,雙擊 app → 直接 raise 既有控制台 + 重新彈瀏覽器
**Settings在桌面控制台內**
- `☑ 啟動時自動開啟瀏覽器`(預設開)
- `☑ 啟動時自動 start server`(預設開)
- `☑ Log 開啟時自動 follow tail`(預設開)
- `☑ Dark mode: Follow system`(唯讀,和第三輪決策一致)
- Language: `[ 繁體中文 ▼ ] / [ English ]`(桌面控制台自己也要 i18n
**例外情境**
- Server 在 5 秒內沒 ready例如 Python sidecar 起不來)→ log 裡已經有錯訊,不彈瀏覽器;使用者看 log 自助排除
- Server 起來但 port 被佔 → 改用 fallback port 並在 header 顯示,`Open in Browser` 連到新 port
---
## C. Web UI vs Desktop Control Panel 職責分野
| 功能 / 面向 | Desktop Control Panel | Web UI瀏覽器| 備註 |
|------------|----------------------|-----------------|------|
| Server 啟動 / 停止 / 重啟 | ✅ **唯一入口** | ❌ | Web UI 不能自殺 |
| Server log 顯示與操作 | ✅ **唯一入口** | ❌ | |
| 「Open in Browser」一鍵跳轉 | ✅ 唯一入口 | — | |
| First-Run 歡迎 / 模式選擇 / 硬體偵測 | ❌ | ✅ | Next.js 現有 flow |
| Dashboard快速開始 + 狀態卡)| ❌ | ✅ | |
| Devices 頁面scan / connect / detail| ❌ | ✅ | |
| Models 頁面(預置 + 上傳 + detail| ❌ | ✅ | |
| Workspace 推論頁面camera / image / video file| ❌ | ✅ | **砍掉 URL tab** |
| Settings > 一般(語言、深色模式狀態) | **部分**(只控制桌面控制台自己)| ✅ | 語言設定**兩套獨立**!見下方問題 |
| Settings > 硬體 / 模型 / 進階 | ❌ | ✅ | |
| OS 通知:裝置連/斷 | ❌App toast 走瀏覽器)| ✅toast| Web 端用瀏覽器 `Notification API` 或 in-page toast |
| OS 通知Server 崩潰 | ✅ **改由控制台負責** | ❌ | 原本第四輪 R4-8 是 Wails shell out現在改成桌面控制台直接呼 OS notification |
| 資料目錄開啟 | ✅ Open log folder 按鈕 | Settings > 進階 也可以 | 兩邊都有,方便 |
| 快捷鍵 ⌘,Settings| ❌ | ✅ | 瀏覽器端 |
| 快捷鍵 ⌘1-4切換主區塊| ❌ | ✅ | |
| 快捷鍵 ⌘Shift+R重新整理裝置| ❌ | ✅ | |
| 快捷鍵 ⌘U上傳模型| ❌ | ✅ | |
| 快捷鍵 ⌘W / ⌘Q | ✅ | ✅(瀏覽器原生關 tab| **衝突**⌘Q 在瀏覽器會結束瀏覽器,誤殺所有 tab見 D 節 |
| i18n 語系 | **自己一套**(控制台)| **自己一套**web app| 兩邊都要雙語,可共用 i18n 辭典 |
| Dark mode | 跟隨系統 | 跟隨系統 | 兩邊都是 CSS `prefers-color-scheme` |
| 品牌 logo / 字體 / 色盤 | **與 Web 一致** | — | Design token 跨兩套 UI 套用 |
**設計決策重點Settings 的語系切換**
目前設計是「Settings > 一般 > 語言」在瀏覽器 web UI 內。桌面控制台**本身**也有文字,必須有自己的語系切換(通常跟系統 locale
兩個選項:
- **方案 1**:桌面控制台只跟系統 locale不提供手動切換最省工符合「控制台是透明的服務管理者」定位
- **方案 2**:桌面控制台 Settings 有獨立語系切換,但預設「跟隨 Web UI 設定」(會需要 IPC 讓兩邊同步)
**Design 建議方案 1**,因為控制台出現的文字量極少(按鈕 + log level
---
## D. 待使用者決策的問題
### D1. 桌面控制台關閉後 server 是否繼續跑? ⚠️ **最重要**
目前第四輪 Q7 決策是「關閉 = 結束」,第三輪 Q-A 決策是「不做 tray」。新架構下這兩個決策打架
| 選項 | 行為 | 代價 | 收穫 |
|------|------|------|------|
| **A. 關閉 = 結束(維持第四輪 Q7** | 關桌面控制台 → server 也停 → 瀏覽器 tab 變磚塊 | 使用者會困惑「我為什麼不能關 log 視窗?」。必須顯示確認對話框「關閉將停止 server 並中斷瀏覽器操作,確定?」(但這本身就是 UX 摩擦) | 簡單、和現有決策一致 |
| **B. 控制台可最小化但不可關閉** | 控制台只能最小化,關閉按鈕 disabled 或變「最小化」 | **非傳統**,違反 OS 慣例macOS 的 `⌘W` / 紅點會壞掉 | 強制維持 server 活著 |
| **C. 背景 daemon 模式(復活 tray** | 關控制台 → 縮回 menu bar / tray → server 繼續跑 → tray 點擊重開控制台 | **正面推翻第三輪 Q-A 決策**tray 跨平台工要做 | 最符合 Docker Desktop / Ollama 模式 |
| **D. 關閉控制台彈警告,使用者選「停 server」或「只關視窗」** | 提供兩個選項 | 決策疲勞,每次關都彈 | 最靈活但最煩 |
**Design 建議:選 C並正式推翻 Q-A**。理由:
- A 會讓使用者每次關控制台都中斷 server完全違背「當成 daemon 用」的 Docker Desktop 心智模型
- B 不符合 OS 慣例,無障礙地雷
- D 每次都問太煩
- C 雖然要投資 tray 做圖資產與 Wails tray第三輪說踩坑但**新架構下 tray 的價值從「可有可無」變成「核心」**值得投資。Ollama / Docker Desktop 都走這條,使用者預期會符合。
**這題使用者必須親自拍板。**
### D2. 首次啟動是否自動彈瀏覽器?
| 選項 | 我的評價 |
|------|---------|
| **A. 自動彈(建議)** | 符合 Ollama / SD WebUI 體驗,降低「我要怎麼開?」摩擦 |
| B. 手動點「Open in Browser」 | 符合 Docker Desktop 體驗,但 Docker Desktop 本身就不需要瀏覽器visionA-local 不彈等於多一步 |
| C. Settings 可切換,預設自動 | 最靈活 |
**建議 C預設 A**
### D3. 瀏覽器端是否需要 authLocalhost-only 還是允許 LAN
目前 server 預設 `:3721` 沒 auth。使用者如果把筆電 hotspot 打開、或連到公共 Wi-Fi`http://筆電-ip:3721` 可能被同網段的人看到推論結果。
| 選項 | 說明 |
|------|------|
| **A. Localhost-only`127.0.0.1:3721`+ 不做 auth建議** | 最安全,符合「單機工具」定位,無 LAN 視窗 |
| B. 綁 `0.0.0.0` + Token auth | 支援 LAN demo副作用使用者 A1 論證 4 可能要這個)但要做 token UI + URL 帶 token + 首次啟動產生 token |
| C. 綁 `0.0.0.0` + 不做 auth | 最危險,絕對不要 |
| D. Settings 可切換,預設 A | 靈活 |
**建議 A**。如果使用者真的要 LAN demofuture backlog現在不做。
### D4. Desktop 控制台支援 Dark Mode跟隨系統
**建議:跟隨系統**(和 Web UI 一致,使用 `prefers-color-scheme`)。不做手動切換。
### D5. Log 保留多少 session
- 畫面內 ring buffer1000 行(建議)
- 磁碟 log 檔:保留最近 7 天 / 7 個 session每個 10MB 上限rotate
- 「Clear log」按鈕是否也清檔案**建議否**,只清畫面
**待確認7 天 / 7 個 session 這個 retention 對使用者夠嗎?**
### D6. Desktop 控制台視窗位置記憶
第一次開:螢幕中央。
第二次開:記住上次關閉時的位置 + 大小嗎?**建議是**,寫到 `~/Library/Application Support/visiona-local/control-panel.json`
### D7. 桌面控制台的 ⌘Q / ⌘W 行為
- `⌘W` 關視窗:在新架構下 = 觸發 D1 的決策Stop/Hide/Ask
- `⌘Q` 結束 app一定 = Stop server + quit
**瀏覽器裡的 ⌘Q** 會結束瀏覽器本身,不是 visionA-local 的問題,使用者本來就會知道。但要在 UI 文案明說「⌘Q 會結束 server」避免誤會。
### D8. Web UI 是否保留任何 Wails 專屬功能?
例如原生檔案對話框、native drag-and-drop、`@wailsapp/runtime` 事件。
**建議:完全不用**。Next.js 在瀏覽器跑就是標準 Web API**徹底切斷** Wails binding這樣未來如果要讓使用者直接在 Chrome 開也能跑。現況 M7-B 已經做到這點,維持即可。
---
## E. 風險觀察
### E1. 使用者認知負荷:兩套 UI 會造成困惑嗎?
**可能的情境**
- 使用者在桌面控制台看到 `Language: [繁中]`,又在 web UI Settings 看到 `語言: [繁中]` — 兩個是同一個還是不同?
- 使用者在 web UI 砍了某個自上傳模型,桌面控制台 log 卻還顯示它?
**緩解**
- 文案要明確:控制台叫 **"Server Control Panel"**web UI 是 **"visionA-local Web"**,兩個名字不要混
- 桌面控制台 UI 極簡,只做服務管理,**完全不碰業務邏輯**(不在這裡管 device、model— 避免使用者以為這裡也能操作
### E2. 「一鍵啟動」變「兩步啟動」是體驗退化嗎?
如果自動開瀏覽器生效 → 體驗等於一鍵。如果沒有 → 退化。
**D2 的決策直接決定這個風險是否存在**
### E3. 既有的 i18n / Design System / Dark Mode 要重新套到桌面控制台嗎?
- **i18n**:要。桌面控制台文字雖少,但仍需中英雙語(符合第二輪 Q13 決策)。**建議**直接讀 web UI 的 `zh-TW.ts / en.ts`,抽一個 `desktop-control` namespace。
- **Design tokens**:要。從 `spec/07-design-tokens.md``color.success / warning / destructive / muted / surface / on-surface`Wails WebView 一樣是 CSS直接用。
- **Dark Mode**:要(和 web UI 一致 `prefers-color-scheme`)。
**這代表桌面控制台不是 Tauri/Electron 原生 widget而是一個 WebView 頁面**(它本來就是 Wails = WebView。這很好因為可以直接複用 Tailwind + shadcn 元件。
### E4. Log 面板的 PII / 敏感資訊風險
Server log 可能會印:
- 檔名(包含使用者的私人檔名)
- 模型名稱
- 裝置 serial
- API 呼叫 body如果 log level = DEBUG
使用者點「Copy log」貼到 Slack 就洩漏了。
**緩解**
- 預設 log level = INFO不印 body
- `Copy log` / `Export log` 前顯示一次性提示「Log 可能包含檔名與裝置資訊,確定要複製?」
- Log 目錄不加密,視 OS 檔案權限保護
### E5. Server 崩潰後桌面控制台需要「自動重啟」嗎?
第四輪 Plan B 有 Python sidecar crash loading + 自動重啟(`spec/08-states.md §8.8`)。
新架構下,控制台自己是 watchdog**可以**直接做 server 層級的 auto-restart例如崩潰 3 秒後自動重啟一次,再崩就紅字等使用者手動)。**這個行為要和 Architect 討論**,因為第四輪原本是 sidecar 層級重啟。
### E6. 「有 log 就有濫用」風險
如果使用者在桌面控制台看到 log 後,**試圖自己寫 script 去 tail log 檔**(例如公司 IT 規範要求把 log 送到 SIEMlog 檔格式就變成一個隱性 API。
**緩解**log 檔格式要穩定,寫進 TDD 的「日誌格式契約」章節(這條以前沒有,可以新增)。
### E7. 新架構和原 PRD 「零依賴、離線可用」承諾一致嗎?
- 瀏覽器?使用者一定有(系統預設)→ ✅
- 127.0.0.1 連線?完全本機 → ✅
- 不需要網路 → ✅
**完全一致,沒問題。**
### E8. 「關閉 Wails 視窗」在新架構下的心智模型衝突
**這個風險是 D1 的延伸**。目前設計決策 Q7 (關閉=結束) 是**假設主視窗是業務 UI** 的前提下做的。主視窗變成控制台後,這個決策的前提崩了。必須重新詢問使用者。
---
## F. 給 PM 和 Architect 的議題
### 給 PM
1. **R4-2 MJPEG 延遲指標的 URL 分母要移除**(原本可能包含 `via url` 情境,砍後只剩 camera + local file
2. **第三方授權頁要更新**yt-dlp 從 vendor 清單移除,安裝檔預期大小從 220MB → 185MB發佈 AC 要重算
3. **RTSP 未來回歸路徑**:使用者說「只做這三種」,要寫到 vision-and-non-goals.md 的非目標清單,避免未來說好的功能被砍後爭議
4. **Persona 更新**新架構下「FAE 帶筆電到客戶現場」的首次啟動腳本要重寫,因為多一個「桌面控制台自動彈 + 瀏覽器自動開」步驟
5. **PRD 產品定位語言**:從「桌面 app」改成「本機服務 + 網頁控制台」— 這是 strategic repositioning不只 UI 改動
6. **Q-A 砍 tray 決策要不要推翻**D1— 這是三方要一起拉回來討論的關鍵點
### 給 Architect
1. **main.go 架構大改**:從「`go:embed all:frontend` + splash redirect」改為「全新 control panel 頁面 + server lifecycle 控制器」。M7-B 的 splash 修復的邏輯大部分可重用(輪詢 `GetServerStatus`),但呈現層完全不同。
2. **`/ipc/raise` endpoint**:從「將 splash 視窗提前」改為「raise 控制台視窗 + open browser」。語意變了。
3. **Single-instance 第二次雙擊 UX**:第四輪 `spec/06-cross-platform.md §6.9` 定義是「靜默 raise」新架構下要不要同時 open browser我的建議順手
4. **OS 通知**:第四輪 R4-8 的 `Server 崩潰 → shell out 原生通知`,新架構下改由桌面控制台直接呼(桌面控制台本來就是 Wails直接 `runtime.MessageDialog` 或用系統 notification API
5. **Log pipeline**server stderr/stdout → 桌面控制台 WebView。需要一個 IPC 或直接讀 log file tail 的機制。建議用 Wails `runtime.EventsEmit` 把新行推到前端
6. **yt-dlp vendor**`make vendor-ytdlp` 任務移除payload-macos / payload-windows / payload-linux 都不再 stage yt-dlp。**Go server 端如果還有任何 `exec.Command("yt-dlp", ...)` 的程式碼路徑,要一起刪**。
7. **ffmpeg 保留**:即使砍 URL 推論ffmpeg 仍要給本地 video file decode 用。R4 的 GPL release blocker 仍然存在。
8. **Port 衝突 fallback**:原架構下 port 佔用是 Next.js 跑不起來 → 整個 app 廢掉。新架構下控制台仍能跑,可以試 fallback port 3722 / 3723並在 header 顯示當前 port
9. **Log 檔案格式契約**E6 風險):需要寫到 TDD 新增章節「日誌格式」,定義欄位與 stability commitment
10. **Desktop control panel 是獨立 routing 還是和 Next.js 共用 WebView**
- 選項 AWails main window load 一個靜態 HTML/JS/CSS不是 Next.js主 UI 跑在瀏覽器 → **推薦**,切得乾淨
- 選項 BWails main window load Next.js 的一個獨立 route `/_desktop-control`,然後瀏覽器跳 `/` → 複用 Next.js 但混在一起Next.js build 會多 chunk
- 建議 A和 M7-B 的 splash 同樣策略:極簡 HTML + vanilla JS或小型 React只做 server 控制和 log 顯示
### 給三方共同討論:
1. **要不要正式推翻「砍 tray」決策**(見 D1這是這次變更的單一最大決策點。
2. **要不要正式把 Q7「關閉=結束」改成「關閉=背景 daemon」**(依賴 1
3. **要不要讓 web UI 也可以綁 LAN**D3這會把產品從「本機單人工具」變成「一人發起多人觀看」的雛形需要 PM 重新評估定位。
4. **新架構下的 First-Run flow 在哪一層跑?**
- 選項 A完全在瀏覽器Next.js控制台只管啟動
- 選項 B桌面控制台也顯示「首次啟動正在準備…」進度條
- 建議 A簡單控制台永遠只做 server 管理。
---
## 結語 / 下一步建議
這次變更的本質是**產品類別變更**:從「桌面軟體」→「本機服務 + 網頁控制台」。如果使用者確定要這樣做,我強烈建議:
1. **先解 D1**(桌面控制台關閉後 server 是否繼續跑 + 是否復活 tray這是最大的架構叉路
2. **再解 D2/D3**啟動行為、LAN 範圍),這些決定預設體驗
3. 三方各自產出正式文件的 deltaPM 更 PRD strategy + feature-inventory + vision-and-non-goalsDesign 更 design-spec §1 IA + §5 replacement for Tray + §6 cross-platform + 新增 §11 Desktop Control PanelArchitect 更 design-doc 架構圖 + main.go 改寫 + api-endpoints + removed-code 加 yt-dlp
4. 然後才進開發
**不要先進開發再補文件。** 這次是第三輪 Q-A 決策的直接推翻 + Q7 決策的間接推翻,如果跳過文件同步,未來不知道為什麼會這樣做。
— Design Agent / 2026-04-14