jim800121chen 8270c4fb0e fix(local-agent): AutoOpenBrowser 預設改 false,啟動不自動開瀏覽器
使用者:local-agent 啟動不需自動開網頁、桌面 GUI 為主。DefaultPreferences
的 AutoOpenBrowser 從 runtime.GOOS!="linux"(mac/win=true)改為所有平台 false。
只影響全新安裝(既有 preferences.json 有明確 autoOpenBrowser 值、Unmarshal
保留,尊重既有設定)。false 分支既有邏輯(SkipStage5 / 不檢查 timeout)未動。
codebase 本就無桌面「Open in Browser」按鈕(DEPRECATED、雲端 UI 遠端設計),
改預設沒弄丟任何手動能力。

reviewer 通過(0C/0M)。build/vet/test 全綠。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-03 12:35:10 +08:00

100 lines
3.7 KiB
Go
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.

package main
// preferences.go — M8-4Wails 控制台偏好設定持久化
//
// TDD ground truth
// - .autoflow/04-architecture/v2/server-lifecycle.md §11「Preferences 持久化」
// - .autoflow/04-architecture/v2/control-panel.md §4.3Preferences struct
//
// 檔案位置:<dataDir>/preferences.json
// 寫入策略atomic write-renametmp → rename避免 crash 中途造成檔案損毀
// 讀取失敗策略fallback 到 DefaultPreferences(),不阻止 app 啟動
import (
"encoding/json"
"fmt"
"os"
"path/filepath"
)
// Preferences 定義控制台偏好。
// 對應 TDD v2/control-panel.md §4.3 的 Preferences struct。
type Preferences struct {
// AutoOpenBrowser — StartServer 成功後是否自動開瀏覽器。
// 預設值由 DefaultPreferences() 決定:所有平台一律 false。
// 桌面 GUI 本身就是主要操作介面,不需要啟動時自動彈瀏覽器分頁;
// 使用者若要用雲端 Web UI自行前往雲端網頁即可。
// (改預設 false 只影響全新安裝 / 無 preferences.json 的使用者;
// 既有使用者的 preferences.json 已明確存有此欄位Load 時尊重既有值。)
AutoOpenBrowser bool `json:"autoOpenBrowser"`
// Locale — 控制台 UI 的語系覆寫;空字串 → 自動偵測navigator.language
Locale string `json:"locale,omitempty"`
// LogRingSize — log panel ring buffer 行數上限。0 → 使用預設 2000。
LogRingSize int `json:"logRingSize,omitempty"`
}
// DefaultPreferences 回傳預設值。
//
// AutoOpenBrowser 一律預設 false桌面 GUI 為主要介面,啟動時不自動開瀏覽器;
// 使用者若要用雲端 Web UI 自行前往雲端網頁。
// (原本 macOS/Windows 預設 true、Linux 預設 false現統一為 false。
func DefaultPreferences() Preferences {
return Preferences{
AutoOpenBrowser: false,
Locale: "",
LogRingSize: 0,
}
}
// preferencesPath 回傳 preferences.json 的絕對路徑。
func preferencesPath(dataDir string) string {
return filepath.Join(dataDir, "preferences.json")
}
// LoadPreferences 讀取 preferences.json。
//
// 讀取失敗 / 檔案不存在 / JSON 損毀 → 回傳 DefaultPreferences(),不回 error。
// (讀取失敗不應阻止 app 啟動;壞檔留在磁碟供使用者 debug。
func LoadPreferences(dataDir string) Preferences {
path := preferencesPath(dataDir)
data, err := os.ReadFile(path)
if err != nil {
// 檔案不存在 / 權限錯誤:用預設
return DefaultPreferences()
}
var p Preferences
if err := json.Unmarshal(data, &p); err != nil {
// JSON 壞了:記一行 warning 到 stderr讓使用者在 log 中看到原因),用預設
fmt.Fprintf(os.Stderr, "[visiona-agent] preferences.json parse failed, using defaults: %v\n", err)
return DefaultPreferences()
}
return p
}
// SavePreferences 把 p 寫入 preferences.json採 atomic write-rename
// 1. 寫到 <path>.tmp
// 2. os.Rename(tmp, real)
//
// Rename 在同一 filesystem 上是 atomic 的,避免寫到一半 crash 造成檔案損毀。
func SavePreferences(dataDir string, p Preferences) error {
if err := os.MkdirAll(dataDir, 0o755); err != nil {
return fmt.Errorf("mkdir data dir: %w", err)
}
data, err := json.MarshalIndent(p, "", " ")
if err != nil {
return fmt.Errorf("marshal preferences: %w", err)
}
realPath := preferencesPath(dataDir)
tmpPath := realPath + ".tmp"
if err := os.WriteFile(tmpPath, data, 0o644); err != nil {
return fmt.Errorf("write tmp: %w", err)
}
if err := os.Rename(tmpPath, realPath); err != nil {
_ = os.Remove(tmpPath) // cleanup 失敗的 tmp
return fmt.Errorf("rename tmp → real: %w", err)
}
return nil
}