jim800121chen c54f16fca0 Initial commit: visionA monorepo with local-tool subproject
local-tool/: visionA-local desktop app
- M1: Wails shell + Go server + Next.js UI + Mock mode (macOS dmg ready)
- M2: i18n (zh-TW/en) + Settings 4-tab refactor
- M3: Embedded Python 3.12 runtime (python-build-standalone) + KneronPLUS wheels
- M4: Windows Inno Setup script (build on Windows runner)
- M5: Linux AppImage script + udev rule (build on Linux runner)
- M6: ffmpeg (GPL, pending legal review) + yt-dlp bundled
- Lifecycle: watchServer health check, fatal native dialog,
            Wails IPC raise endpoint, stale process cleanup

.autoflow/: full PRD / Design Spec / Architecture / Testing docs
            (4 rounds tri-party discussion + cross review)
.github/workflows/: macOS / Windows / Linux build CI

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-11 22:10:38 +08:00

177 lines
9.2 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.

# 06 — 跨平台 UX 差異處理
原則:**視覺層統一、互動層遵循平台慣例。**
## 6.1 Window Chrome視窗標題列
| 平台 | 策略 | 理由 |
|------|------|------|
| **macOS** | frameless + 顯示 traffic lights紅綠黃於左上 | 最原生的感覺Wails 支援 `TitleBar: { Hide: true }` + 保留 traffic lights |
| **Windows** | 標準 title bar系統提供 | 不自畫 close/minimize避免 DPI / 深色模式坑 |
| **Linux** | 標準 title barGTK/Qt | 同上,讓 WM 決定 |
**標題列內容**App 名稱 `visionA-local`所有平台統一。macOS 的 frameless 沒有標題列文字,改把 page title 放在 header 區塊裡即可(見 03-wireframes 的 Header
**最小化 / 最大化行為**:全平台一致 — 都能最小化、最大化、調整視窗大小。最小視窗尺寸 **960×640**(保證 sidebar + content 不擠壓)。
## 6.2 快捷鍵
### 修飾鍵對照
| 動作 | macOS | Windows / Linux |
|------|-------|----------------|
| Settings | ⌘ , | Ctrl + , |
| 結束 | ⌘ Q | Ctrl + Q或 Alt + F4 |
| 關閉視窗(=結束程式) | ⌘ W | Ctrl + W |
| 切換主區塊Dashboard/Models/Devices/Workspace | ⌘ 14 | Ctrl + 14 |
| 重新整理裝置 | ⌘ Shift + R | Ctrl + Shift + R |
| 上傳模型 | ⌘ U | Ctrl + U |
**⌘W 語意**:因為決策 Q7 為「關閉視窗 = 結束程式」、且決策 Q-A 砍掉 tray⌘W 與 ⌘Q 行為一致(關閉唯一主視窗即結束 App
**第四輪 R4-6 決策變更**
- **`⌘R``⌘Shift+R`**:原本 `⌘R` 會與 WebView 內建的「重新載入頁面」衝突(使用者一按可能誤觸前端整頁 reload改為 `⌘Shift+R` 作為「重新掃描裝置」的明確動作,避免與瀏覽器反射行為打架。
- **取消原 `⌘Shift+W 前往 Workspace`**:此組合與 macOS 內建「關閉所有視窗」衝突,且 `⌘4` 已涵蓋「切到 Workspace」的需求移除避免語意重疊。
**實作方式**:所有快捷鍵定義在 Wails 的原生 menu API由 OS 派送;**不要**在前端用 `keydown` 監聽(會與輸入框衝突且跨平台不一致)。
**衝突避免**
- 不佔用 ⌘ X/C/V/A/Z輸入操作
- 不佔用 ⌘ F應該給頁面內搜尋用
### 原生 App 選單macOS 獨有)
macOS 需要一個原生 menu bar因為無 window menu
```
visionA-local | File | Edit | View | Devices | Help
```
- **visionA-local**About、Preferences (⌘,)、Services、Hide、Quit
- **File**:新增裝置掃描、上傳模型、匯出結果
- **Edit**Undo / Redo / Cut / Copy / Paste標準 macOS
- **View**:切到 Dashboard/Models/Devices/Workspace (⌘1-4)、重新載入
- **Devices**:重新掃描、切換模式、連線紀錄
- **Help**:說明文件、關於 visionA-local、回報問題開外部瀏覽器
Windows/Linux 不做原生 menu bar使用者會覺得多餘改放在 Settings / 頁面內。
## 6.3 檔案對話框
| 場景 | 實作 |
|------|------|
| 上傳 `.nef` 模型 | Wails `runtime.OpenFileDialog`filter `*.nef` |
| 選擇影片檔 | Wails `runtime.OpenFileDialog`filter `*.mp4,*.mov,*.avi` |
| 選擇圖片批次 | Wails `runtime.OpenMultipleFilesDialog`filter `image/*` |
| 匯出結果 | Wails `runtime.SaveFileDialog` |
| 「在 Finder/Explorer 顯示」 | 平台指令:`open` (mac) / `explorer /select,` (win) / `xdg-open` (linux) |
**禁止**:自畫 file picker、用 HTML `<input type=file>`(桌面 app 應該用原生)。
**拖放**:支援 drag-and-drop 到整個 Models / Workspace 頁面,由前端處理 drop eventWails 會傳 file path 給 Go 端。
## 6.4 通知(第四輪 R4-8 定案)
**策略原則**
- **App 內 toast** 處理一般資訊、裝置連/斷等前景事件(使用者必然在看著主視窗,不必打擾 OS 通知中心)
- **OS 原生通知** 只用於「App 可能不在前景、且必須立刻知道」的嚴重事件(主要是 Server 崩潰)
| 場景 | 通知方式 | 說明 |
|------|---------|------|
| 裝置連接成功 | **App 內 toast**success | 關閉 = 結束程式,使用者必然在主視窗前,不需要 OS 通知 |
| 裝置斷線 | **App 內 toast**warning | 同上 |
| 模型上傳完成 | App 內 toastsuccess | — |
| 模型載入失敗 | App 內 toastdestructive | — |
| **Server 崩潰 / 自動重啟** | **OS 原生通知**(嚴重) | 崩潰時前端可能也沒了,唯有 OS 通知可靠 |
| 推論結果(使用者要求時) | App 內 toast / timeline 更新 | — |
| 一般資訊(設定已儲存等) | App 內 toast | — |
**實作方式**shell outWails 原生通知 API 有限):
- **macOS**`osascript -e 'display notification "訊息" with title "visionA-local"'`
- **Windows**PowerShell `New-BurntToastNotification -Text "訊息"`(需內建 BurntToast 模組,若沒有則退回 msg box
- **Linux**`notify-send "visionA-local" "訊息"`
**需 Architect**在後端提供統一封裝,前端只需呼叫 `/api/notify` 或透過 Wails Go↔JS bridge 觸發。
## 6.9 Single-instance單例行為
**情境**:使用者二次雙擊 app 圖示、或從 Dock/Start menu 再次啟動 visiona-local但 App 已在執行。
**策略**(與 Architect `tray-and-lifecycle.md §2.3` 對齊):**靜默 raise不彈 toast、不彈對話框**。
| 動作 | 預期行為 |
|------|---------|
| 第二個程序啟動 | 偵測到 lock file → 呼叫既有程序的 `/ipc/raise` → 既有程序 `WindowShow(ctx)` 把主視窗帶到最前並聚焦 → 第二個程序立即退出exit 0 |
| 視覺回饋 | **無 toast、無對話框**。使用者看到主視窗被帶到前景即可(這本身就是最直觀的回饋) |
| Dock clickmacOS | 由 Cocoa 自動處理,無需介入 |
| `/ipc/raise` 失敗(例如 IPC port 讀不到) | **顯示 error modal**「無法喚起既有的 visiona-local 視窗,請手動切換」+「確認」按鈕 → 第二個程序退出。**絕對不覆寫 lock 另開新視窗**(會出現兩個視窗,體驗極差) |
**設計理由**
- 彈 toast「已有另一個 visionA-local 在執行中」是**冗餘資訊**——使用者會看到視窗浮起來,不需要再被告知一次
- 但 raise 失敗的錯誤 modal 必要,否則使用者會以為 app 壞了而反覆雙擊
**與 i18n 的互動**error modal 的文案走 `errors:singleInstance.raiseFailed`,需要中英兩版。
## 6.5 字型
| 平台 | 字型 |
|------|------|
| macOS | `-apple-system, "SF Pro Text", "PingFang TC"` |
| Windows | `"Segoe UI", "Microsoft JhengHei"` |
| Linux | `"Inter", "Noto Sans TC", "Ubuntu"` |
CSS font-family 一字串包辦:
```css
font-family:
-apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang TC",
"Microsoft JhengHei", "Noto Sans TC", Inter, Ubuntu,
Roboto, sans-serif;
```
等寬字型log viewer
```css
font-family:
ui-monospace, SFMono-Regular, "SF Mono", Menlo,
Consolas, "Cascadia Mono", monospace;
```
## 6.6 Tray icon 資產
~~見 `05-tray.md`。~~
**已於第三輪決策 Q-A 砍除 tray**`05-tray.md` 刪檔)。本版不做 trayApp 只透過主視窗與原生 menu bar僅 macOS互動。
## 6.7 資料目錄位置
| 平台 | 路徑 |
|------|------|
| macOS | `~/Library/Application Support/visiona-local/` |
| Windows | `%APPDATA%\visiona-local\` |
| Linux | `~/.local/share/visiona-local/` |
**UI 顯示**Settings > 一般 統一顯示為「資料目錄:[完整路徑]」並附「在 Finder/Explorer 顯示」按鈕,使用者不需要記平台差異。
**第三輪決策 Q-E1** 採 OS 慣例路徑;**第四輪 R4-5** 進一步決定目錄名稱**全小寫**`visiona-local` 而非 `visionA-local`),理由:
- 對齊 Bundle ID `com.innovedus.visiona-local`
- 符合 Linux 檔案系統慣例case-sensitive且習慣全小寫
- 避免 macOS/Windowscase-insensitive與 Linux 產生不一致
- App 顯示名稱「visionA-local」不受影響僅檔案系統層路徑全小寫
## 6.8 深色模式
**統一方案(第四輪定案)****純 CSS `prefers-color-scheme` media query + Tailwind `dark:` variant****不需要 Go/Wails 在系統主題變更時 emit event 給前端**。
| 平台 | 偵測方式 |
|------|---------|
| macOS | WebView 自動支援 `prefers-color-scheme`Cocoa → WKWebView 會在系統切換 light/dark 時自動更新 media query 結果) |
| Windows 10/11 | WebView2Edge Chromium原生支援 `prefers-color-scheme` |
| Linux | WebKit2GTK 支援 `prefers-color-scheme`(需 GTK 系統主題正確設定) |
**實作簡化理由**
- 三平台 WebView 都已原生支援 `prefers-color-scheme` 的即時切換(使用者在系統設定切 light/dark 的瞬間CSS media query 就會重新 match前端 re-render 即可)
- **不需要** Wails Go 端監聽系統主題事件再 emit 到前端(原本規劃這樣做是為了 fallback但實測多餘
- 前端只要用 `@media (prefers-color-scheme: dark) { ... }` 或 Tailwind `dark:` variant無需 JS 介入
**不提供手動切換**(決策 Q15。Settings > 一般 只顯示唯讀的「主題:跟隨系統(目前:深色/淺色)」,其中「目前」狀態用 `window.matchMedia('(prefers-color-scheme: dark)').matches` 讀取。