visionA/local-tool/.autoflow/03-design/spec/06-cross-platform.md
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

9.2 KiB
Raw Blame History

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-localAbout、Preferences (⌘,)、Services、Hide、Quit
  • File:新增裝置掃描、上傳模型、匯出結果
  • EditUndo / 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.OpenFileDialogfilter *.nef
選擇影片檔 Wails runtime.OpenFileDialogfilter *.mp4,*.mov,*.avi
選擇圖片批次 Wails runtime.OpenMultipleFilesDialogfilter 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 內 toastsuccess 關閉 = 結束程式,使用者必然在主視窗前,不需要 OS 通知
裝置斷線 App 內 toastwarning 同上
模型上傳完成 App 內 toastsuccess
模型載入失敗 App 內 toastdestructive
Server 崩潰 / 自動重啟 OS 原生通知(嚴重) 崩潰時前端可能也沒了,唯有 OS 通知可靠
推論結果(使用者要求時) App 內 toast / timeline 更新
一般資訊(設定已儲存等) App 內 toast

實作方式shell outWails 原生通知 API 有限):

  • macOSosascript -e 'display notification "訊息" with title "visionA-local"'
  • WindowsPowerShell New-BurntToastNotification -Text "訊息"(需內建 BurntToast 模組,若沒有則退回 msg box
  • Linuxnotify-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 一字串包辦:

font-family:
  -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang TC",
  "Microsoft JhengHei", "Noto Sans TC", Inter, Ubuntu,
  Roboto, sans-serif;

等寬字型log viewer

font-family:
  ui-monospace, SFMono-Regular, "SF Mono", Menlo,
  Consolas, "Cascadia Mono", monospace;

6.6 Tray icon 資產

05-tray.md 已於第三輪決策 Q-A 砍除 tray05-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-schemeCocoa → 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 讀取。