refactor(config): add runtime config snapshot
- commit
- aebb5c187c6bcfe76e51334e2598b6812a4ed5e6
- author
- brice · · 2026-06-23 13:59 UTC
- committer
- brice · · 2026-06-23 20:01 UTC
- parent
- 84747b2
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
3 files · +142 -0
A internal/application/port/runtime_config.go +53 -0
| @@ -0,0 +1,53 @@ | |||
| 1 | + | package port | |
| 2 | + | ||
| 3 | + | import "github.com/bnema/dumber/internal/domain/entity" | |
| 4 | + | ||
| 5 | + | type RuntimeConfigProvider interface { | |
| 6 | + | Current() RuntimeConfigSnapshot | |
| 7 | + | Watch() error | |
| 8 | + | OnChange(func(RuntimeConfigSnapshot)) | |
| 9 | + | } | |
| 10 | + | ||
| 11 | + | type RuntimeConfigSnapshot struct { | |
| 12 | + | EngineSettings EngineSettingsPayload | |
| 13 | + | UI RuntimeUIConfig | |
| 14 | + | } | |
| 15 | + | ||
| 16 | + | type RuntimeUIConfig struct { | |
| 17 | + | DefaultUIScale float64 | |
| 18 | + | SidebarWidth int | |
| 19 | + | Appearance entity.AppearanceConfig | |
| 20 | + | Workspace entity.WorkspaceConfig | |
| 21 | + | Session entity.SessionConfig | |
| 22 | + | Clipboard RuntimeClipboardConfig | |
| 23 | + | SearchShortcuts map[string]RuntimeSearchShortcut | |
| 24 | + | DefaultSearchEngine string | |
| 25 | + | Omnibox RuntimeOmniboxConfig | |
| 26 | + | Update RuntimeUpdateConfig | |
| 27 | + | Downloads RuntimeDownloadsConfig | |
| 28 | + | } | |
| 29 | + | ||
| 30 | + | type RuntimeClipboardConfig struct { | |
| 31 | + | AutoCopyOnSelection bool | |
| 32 | + | } | |
| 33 | + | ||
| 34 | + | type RuntimeSearchShortcut struct { | |
| 35 | + | URL string | |
| 36 | + | Description string | |
| 37 | + | } | |
| 38 | + | ||
| 39 | + | type RuntimeOmniboxConfig struct { | |
| 40 | + | InitialBehavior entity.OmniboxInitialBehavior | |
| 41 | + | MostVisitedDays int | |
| 42 | + | AutoOpenOnNewPane bool | |
| 43 | + | } | |
| 44 | + | ||
| 45 | + | type RuntimeUpdateConfig struct { | |
| 46 | + | EnableOnStartup bool | |
| 47 | + | AutoDownload bool | |
| 48 | + | NotifyOnNewSettings bool | |
| 49 | + | } | |
| 50 | + | ||
| 51 | + | type RuntimeDownloadsConfig struct { | |
| 52 | + | Path string | |
| 53 | + | } | |
| @@ -0,0 +1,53 @@ | |||||
| 1 | + | package port | |||
| 2 | + | ||||
| 3 | + | import "github.com/bnema/dumber/internal/domain/entity" | |||
| 4 | + | ||||
| 5 | + | type RuntimeConfigProvider interface { | |||
| 6 | + | Current() RuntimeConfigSnapshot | |||
| 7 | + | Watch() error | |||
| 8 | + | OnChange(func(RuntimeConfigSnapshot)) | |||
| 9 | + | } | |||
| 10 | + | ||||
| 11 | + | type RuntimeConfigSnapshot struct { | |||
| 12 | + | EngineSettings EngineSettingsPayload | |||
| 13 | + | UI RuntimeUIConfig | |||
| 14 | + | } | |||
| 15 | + | ||||
| 16 | + | type RuntimeUIConfig struct { | |||
| 17 | + | DefaultUIScale float64 | |||
| 18 | + | SidebarWidth int | |||
| 19 | + | Appearance entity.AppearanceConfig | |||
| 20 | + | Workspace entity.WorkspaceConfig | |||
| 21 | + | Session entity.SessionConfig | |||
| 22 | + | Clipboard RuntimeClipboardConfig | |||
| 23 | + | SearchShortcuts map[string]RuntimeSearchShortcut | |||
| 24 | + | DefaultSearchEngine string | |||
| 25 | + | Omnibox RuntimeOmniboxConfig | |||
| 26 | + | Update RuntimeUpdateConfig | |||
| 27 | + | Downloads RuntimeDownloadsConfig | |||
| 28 | + | } | |||
| 29 | + | ||||
| 30 | + | type RuntimeClipboardConfig struct { | |||
| 31 | + | AutoCopyOnSelection bool | |||
| 32 | + | } | |||
| 33 | + | ||||
| 34 | + | type RuntimeSearchShortcut struct { | |||
| 35 | + | URL string | |||
| 36 | + | Description string | |||
| 37 | + | } | |||
| 38 | + | ||||
| 39 | + | type RuntimeOmniboxConfig struct { | |||
| 40 | + | InitialBehavior entity.OmniboxInitialBehavior | |||
| 41 | + | MostVisitedDays int | |||
| 42 | + | AutoOpenOnNewPane bool | |||
| 43 | + | } | |||
| 44 | + | ||||
| 45 | + | type RuntimeUpdateConfig struct { | |||
| 46 | + | EnableOnStartup bool | |||
| 47 | + | AutoDownload bool | |||
| 48 | + | NotifyOnNewSettings bool | |||
| 49 | + | } | |||
| 50 | + | ||||
| 51 | + | type RuntimeDownloadsConfig struct { | |||
| 52 | + | Path string | |||
| 53 | + | } | |||
M internal/bootstrap/runtime_config.go +41 -0
| @@ -24,6 +24,47 @@ func EngineSettingsPayloadFromConfig(cfg *config.Config) port.EngineSettingsPayl | |||
| 24 | 24 | } | |
| 25 | 25 | } | |
| 26 | 26 | ||
| 27 | + | func RuntimeConfigSnapshotFromConfig(cfg *config.Config) port.RuntimeConfigSnapshot { | |
| 28 | + | if cfg == nil { | |
| 29 | + | return port.RuntimeConfigSnapshot{} | |
| 30 | + | } | |
| 31 | + | return port.RuntimeConfigSnapshot{ | |
| 32 | + | EngineSettings: EngineSettingsPayloadFromConfig(cfg), | |
| 33 | + | UI: port.RuntimeUIConfig{ | |
| 34 | + | DefaultUIScale: cfg.DefaultUIScale, | |
| 35 | + | SidebarWidth: cfg.SidebarWidth, | |
| 36 | + | Appearance: cfg.Appearance, | |
| 37 | + | Workspace: cfg.Workspace, | |
| 38 | + | Session: cfg.Session, | |
| 39 | + | Clipboard: port.RuntimeClipboardConfig{AutoCopyOnSelection: cfg.Clipboard.AutoCopyOnSelection}, | |
| 40 | + | SearchShortcuts: runtimeSearchShortcutsFromConfig(cfg.SearchShortcuts), | |
| 41 | + | DefaultSearchEngine: cfg.DefaultSearchEngine, | |
| 42 | + | Omnibox: port.RuntimeOmniboxConfig{ | |
| 43 | + | InitialBehavior: cfg.Omnibox.InitialBehavior, | |
| 44 | + | MostVisitedDays: cfg.Omnibox.MostVisitedDays, | |
| 45 | + | AutoOpenOnNewPane: cfg.Omnibox.AutoOpenOnNewPane, | |
| 46 | + | }, | |
| 47 | + | Update: port.RuntimeUpdateConfig{ | |
| 48 | + | EnableOnStartup: cfg.Update.EnableOnStartup, | |
| 49 | + | AutoDownload: cfg.Update.AutoDownload, | |
| 50 | + | NotifyOnNewSettings: cfg.Update.NotifyOnNewSettings, | |
| 51 | + | }, | |
| 52 | + | Downloads: port.RuntimeDownloadsConfig{Path: cfg.Downloads.Path}, | |
| 53 | + | }, | |
| 54 | + | } | |
| 55 | + | } | |
| 56 | + | ||
| 57 | + | func runtimeSearchShortcutsFromConfig(in map[string]config.SearchShortcut) map[string]port.RuntimeSearchShortcut { | |
| 58 | + | out := make(map[string]port.RuntimeSearchShortcut, len(in)) | |
| 59 | + | for key, shortcut := range in { | |
| 60 | + | out[key] = port.RuntimeSearchShortcut{ | |
| 61 | + | URL: shortcut.URL, | |
| 62 | + | Description: shortcut.Description, | |
| 63 | + | } | |
| 64 | + | } | |
| 65 | + | return out | |
| 66 | + | } | |
| 67 | + | ||
| 27 | 68 | func engineHardwareDecodingModeFromConfig(mode config.HardwareDecodingMode) port.EngineHardwareDecodingMode { | |
| 28 | 69 | switch mode { | |
| 29 | 70 | case config.HardwareDecodingForce: | |
| @@ -24,6 +24,47 @@ func EngineSettingsPayloadFromConfig(cfg *config.Config) port.EngineSettingsPayl | |||||
| 24 | } | 24 | } | ||
| 25 | } | 25 | } | ||
| 26 | 26 | ||||
| 27 | + | func RuntimeConfigSnapshotFromConfig(cfg *config.Config) port.RuntimeConfigSnapshot { | |||
| 28 | + | if cfg == nil { | |||
| 29 | + | return port.RuntimeConfigSnapshot{} | |||
| 30 | + | } | |||
| 31 | + | return port.RuntimeConfigSnapshot{ | |||
| 32 | + | EngineSettings: EngineSettingsPayloadFromConfig(cfg), | |||
| 33 | + | UI: port.RuntimeUIConfig{ | |||
| 34 | + | DefaultUIScale: cfg.DefaultUIScale, | |||
| 35 | + | SidebarWidth: cfg.SidebarWidth, | |||
| 36 | + | Appearance: cfg.Appearance, | |||
| 37 | + | Workspace: cfg.Workspace, | |||
| 38 | + | Session: cfg.Session, | |||
| 39 | + | Clipboard: port.RuntimeClipboardConfig{AutoCopyOnSelection: cfg.Clipboard.AutoCopyOnSelection}, | |||
| 40 | + | SearchShortcuts: runtimeSearchShortcutsFromConfig(cfg.SearchShortcuts), | |||
| 41 | + | DefaultSearchEngine: cfg.DefaultSearchEngine, | |||
| 42 | + | Omnibox: port.RuntimeOmniboxConfig{ | |||
| 43 | + | InitialBehavior: cfg.Omnibox.InitialBehavior, | |||
| 44 | + | MostVisitedDays: cfg.Omnibox.MostVisitedDays, | |||
| 45 | + | AutoOpenOnNewPane: cfg.Omnibox.AutoOpenOnNewPane, | |||
| 46 | + | }, | |||
| 47 | + | Update: port.RuntimeUpdateConfig{ | |||
| 48 | + | EnableOnStartup: cfg.Update.EnableOnStartup, | |||
| 49 | + | AutoDownload: cfg.Update.AutoDownload, | |||
| 50 | + | NotifyOnNewSettings: cfg.Update.NotifyOnNewSettings, | |||
| 51 | + | }, | |||
| 52 | + | Downloads: port.RuntimeDownloadsConfig{Path: cfg.Downloads.Path}, | |||
| 53 | + | }, | |||
| 54 | + | } | |||
| 55 | + | } | |||
| 56 | + | ||||
| 57 | + | func runtimeSearchShortcutsFromConfig(in map[string]config.SearchShortcut) map[string]port.RuntimeSearchShortcut { | |||
| 58 | + | out := make(map[string]port.RuntimeSearchShortcut, len(in)) | |||
| 59 | + | for key, shortcut := range in { | |||
| 60 | + | out[key] = port.RuntimeSearchShortcut{ | |||
| 61 | + | URL: shortcut.URL, | |||
| 62 | + | Description: shortcut.Description, | |||
| 63 | + | } | |||
| 64 | + | } | |||
| 65 | + | return out | |||
| 66 | + | } | |||
| 67 | + | ||||
| 27 | func engineHardwareDecodingModeFromConfig(mode config.HardwareDecodingMode) port.EngineHardwareDecodingMode { | 68 | func engineHardwareDecodingModeFromConfig(mode config.HardwareDecodingMode) port.EngineHardwareDecodingMode { | ||
| 28 | switch mode { | 69 | switch mode { | ||
| 29 | case config.HardwareDecodingForce: | 70 | case config.HardwareDecodingForce: | ||
M internal/bootstrap/runtime_config_test.go +48 -0
| @@ -1,6 +1,7 @@ | |||
| 1 | 1 | package bootstrap | |
| 2 | 2 | ||
| 3 | 3 | import ( | |
| 4 | + | "reflect" | |
| 4 | 5 | "testing" | |
| 5 | 6 | ||
| 6 | 7 | "github.com/bnema/dumber/internal/application/port" | |
| @@ -47,6 +48,53 @@ func TestEngineSettingsPayloadFromNilConfigReturnsZeroPayload(t *testing.T) { | |||
| 47 | 48 | } | |
| 48 | 49 | } | |
| 49 | 50 | ||
| 51 | + | func TestRuntimeConfigSnapshotFromConfigMapsUIRuntimeFields(t *testing.T) { | |
| 52 | + | cfg := config.DefaultConfig() | |
| 53 | + | cfg.DefaultUIScale = 1.4 | |
| 54 | + | cfg.SidebarWidth = 333 | |
| 55 | + | cfg.Downloads.Path = "/tmp/downloads" | |
| 56 | + | cfg.DefaultSearchEngine = "https://search.example/?q=%s" | |
| 57 | + | cfg.SearchShortcuts = map[string]config.SearchShortcut{ | |
| 58 | + | "gh": {URL: "https://github.com/search?q=%s", Description: "GitHub"}, | |
| 59 | + | } | |
| 60 | + | cfg.Workspace.NewPaneURL = "about:blank" | |
| 61 | + | cfg.Session.SnapshotIntervalMs = 7000 | |
| 62 | + | cfg.Clipboard.AutoCopyOnSelection = true | |
| 63 | + | cfg.Omnibox.AutoOpenOnNewPane = true | |
| 64 | + | cfg.Update.NotifyOnNewSettings = true | |
| 65 | + | ||
| 66 | + | got := RuntimeConfigSnapshotFromConfig(cfg) | |
| 67 | + | ||
| 68 | + | if got.EngineSettings != EngineSettingsPayloadFromConfig(cfg) { | |
| 69 | + | t.Fatalf("EngineSettings=%#v, want %#v", got.EngineSettings, EngineSettingsPayloadFromConfig(cfg)) | |
| 70 | + | } | |
| 71 | + | if got.UI.DefaultUIScale != 1.4 || | |
| 72 | + | got.UI.SidebarWidth != 333 || | |
| 73 | + | got.UI.Downloads.Path != "/tmp/downloads" || | |
| 74 | + | got.UI.DefaultSearchEngine != "https://search.example/?q=%s" || | |
| 75 | + | got.UI.Workspace.NewPaneURL != "about:blank" || | |
| 76 | + | got.UI.Session.SnapshotIntervalMs != 7000 || | |
| 77 | + | !got.UI.Clipboard.AutoCopyOnSelection || | |
| 78 | + | !got.UI.Omnibox.AutoOpenOnNewPane || | |
| 79 | + | !got.UI.Update.NotifyOnNewSettings { | |
| 80 | + | t.Fatalf("snapshot not mapped: %#v", got.UI) | |
| 81 | + | } | |
| 82 | + | if got.UI.SearchShortcuts["gh"].URL != "https://github.com/search?q=%s" { | |
| 83 | + | t.Fatalf("search shortcut not mapped: %#v", got.UI.SearchShortcuts) | |
| 84 | + | } | |
| 85 | + | got.UI.SearchShortcuts["gh"] = port.RuntimeSearchShortcut{URL: "mutated"} | |
| 86 | + | if cfg.SearchShortcuts["gh"].URL == "mutated" { | |
| 87 | + | t.Fatal("snapshot must deep-copy search shortcut map") | |
| 88 | + | } | |
| 89 | + | } | |
| 90 | + | ||
| 91 | + | func TestRuntimeConfigSnapshotFromNilConfigReturnsZeroSnapshot(t *testing.T) { | |
| 92 | + | got := RuntimeConfigSnapshotFromConfig(nil) | |
| 93 | + | if !reflect.DeepEqual(got, port.RuntimeConfigSnapshot{}) { | |
| 94 | + | t.Fatalf("snapshot=%#v, want zero value", got) | |
| 95 | + | } | |
| 96 | + | } | |
| 97 | + | ||
| 50 | 98 | func TestEngineSettingsPayloadFromConfigMapsHardwareDecodingModes(t *testing.T) { | |
| 51 | 99 | tests := []struct { | |
| 52 | 100 | name string | |
| @@ -1,6 +1,7 @@ | |||||
| 1 | package bootstrap | 1 | package bootstrap | ||
| 2 | 2 | ||||
| 3 | import ( | 3 | import ( | ||
| 4 | + | "reflect" | |||
| 4 | "testing" | 5 | "testing" | ||
| 5 | 6 | ||||
| 6 | "github.com/bnema/dumber/internal/application/port" | 7 | "github.com/bnema/dumber/internal/application/port" | ||
| @@ -47,6 +48,53 @@ func TestEngineSettingsPayloadFromNilConfigReturnsZeroPayload(t *testing.T) { | |||||
| 47 | } | 48 | } | ||
| 48 | } | 49 | } | ||
| 49 | 50 | ||||
| 51 | + | func TestRuntimeConfigSnapshotFromConfigMapsUIRuntimeFields(t *testing.T) { | |||
| 52 | + | cfg := config.DefaultConfig() | |||
| 53 | + | cfg.DefaultUIScale = 1.4 | |||
| 54 | + | cfg.SidebarWidth = 333 | |||
| 55 | + | cfg.Downloads.Path = "/tmp/downloads" | |||
| 56 | + | cfg.DefaultSearchEngine = "https://search.example/?q=%s" | |||
| 57 | + | cfg.SearchShortcuts = map[string]config.SearchShortcut{ | |||
| 58 | + | "gh": {URL: "https://github.com/search?q=%s", Description: "GitHub"}, | |||
| 59 | + | } | |||
| 60 | + | cfg.Workspace.NewPaneURL = "about:blank" | |||
| 61 | + | cfg.Session.SnapshotIntervalMs = 7000 | |||
| 62 | + | cfg.Clipboard.AutoCopyOnSelection = true | |||
| 63 | + | cfg.Omnibox.AutoOpenOnNewPane = true | |||
| 64 | + | cfg.Update.NotifyOnNewSettings = true | |||
| 65 | + | ||||
| 66 | + | got := RuntimeConfigSnapshotFromConfig(cfg) | |||
| 67 | + | ||||
| 68 | + | if got.EngineSettings != EngineSettingsPayloadFromConfig(cfg) { | |||
| 69 | + | t.Fatalf("EngineSettings=%#v, want %#v", got.EngineSettings, EngineSettingsPayloadFromConfig(cfg)) | |||
| 70 | + | } | |||
| 71 | + | if got.UI.DefaultUIScale != 1.4 || | |||
| 72 | + | got.UI.SidebarWidth != 333 || | |||
| 73 | + | got.UI.Downloads.Path != "/tmp/downloads" || | |||
| 74 | + | got.UI.DefaultSearchEngine != "https://search.example/?q=%s" || | |||
| 75 | + | got.UI.Workspace.NewPaneURL != "about:blank" || | |||
| 76 | + | got.UI.Session.SnapshotIntervalMs != 7000 || | |||
| 77 | + | !got.UI.Clipboard.AutoCopyOnSelection || | |||
| 78 | + | !got.UI.Omnibox.AutoOpenOnNewPane || | |||
| 79 | + | !got.UI.Update.NotifyOnNewSettings { | |||
| 80 | + | t.Fatalf("snapshot not mapped: %#v", got.UI) | |||
| 81 | + | } | |||
| 82 | + | if got.UI.SearchShortcuts["gh"].URL != "https://github.com/search?q=%s" { | |||
| 83 | + | t.Fatalf("search shortcut not mapped: %#v", got.UI.SearchShortcuts) | |||
| 84 | + | } | |||
| 85 | + | got.UI.SearchShortcuts["gh"] = port.RuntimeSearchShortcut{URL: "mutated"} | |||
| 86 | + | if cfg.SearchShortcuts["gh"].URL == "mutated" { | |||
| 87 | + | t.Fatal("snapshot must deep-copy search shortcut map") | |||
| 88 | + | } | |||
| 89 | + | } | |||
| 90 | + | ||||
| 91 | + | func TestRuntimeConfigSnapshotFromNilConfigReturnsZeroSnapshot(t *testing.T) { | |||
| 92 | + | got := RuntimeConfigSnapshotFromConfig(nil) | |||
| 93 | + | if !reflect.DeepEqual(got, port.RuntimeConfigSnapshot{}) { | |||
| 94 | + | t.Fatalf("snapshot=%#v, want zero value", got) | |||
| 95 | + | } | |||
| 96 | + | } | |||
| 97 | + | ||||
| 50 | func TestEngineSettingsPayloadFromConfigMapsHardwareDecodingModes(t *testing.T) { | 98 | func TestEngineSettingsPayloadFromConfigMapsHardwareDecodingModes(t *testing.T) { | ||
| 51 | tests := []struct { | 99 | tests := []struct { | ||
| 52 | name string | 100 | name string | ||