refactor(ui): complete runtime config provider migration
- commit
- 3ada45bd412a3ddecd1c4229dc20a81caff50801
- author
- brice · · 2026-06-23 15:47 UTC
- committer
- brice · · 2026-06-23 20:01 UTC
- parent
- ccbb812
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
14 files · +336 -252
M cmd/dumber/main.go +3 -21
| @@ -868,12 +868,8 @@ func buildUIDependencies( | |||
| 868 | 868 | runtimeConfig := bootstrap.NewRuntimeConfigProvider(cfg, config.GetManager()) | |
| 869 | 869 | ||
| 870 | 870 | uiDeps := &ui.Dependencies{ | |
| 871 | - | Ctx: ctx, | |
| 872 | - | Config: cfg, | |
| 873 | - | RuntimeConfig: runtimeConfig, | |
| 874 | - | EngineSettingsPayload: func() port.EngineSettingsPayload { | |
| 875 | - | return bootstrap.EngineSettingsPayloadFromConfig(cfg) | |
| 876 | - | }, | |
| 871 | + | Ctx: ctx, | |
| 872 | + | RuntimeConfig: runtimeConfig, | |
| 877 | 873 | InitialURL: initialURL, | |
| 878 | 874 | RestoreSessionID: restoreSessionID, | |
| 879 | 875 | StartupCrashReports: startupCrashReports, | |
| @@ -927,26 +923,12 @@ func buildUIDependencies( | |||
| 927 | 923 | NewGTKEntryTarget: func(entry *gtk.SearchEntry) port.TextInputTarget { | |
| 928 | 924 | return textinput.NewGTKEntryTarget(entry) | |
| 929 | 925 | }, | |
| 930 | - | OnConfigChange: func(callback func()) { | |
| 931 | - | config.GetManager().OnConfigChange(func(newCfg *config.Config) { | |
| 932 | - | // No synchronization needed: viper fires this callback on the | |
| 933 | - | // GTK main thread (via glib.IdleAdd), and all callers of cfg run | |
| 934 | - | // on the same GTK main thread, so there is no concurrent access. | |
| 935 | - | if cfg != nil && newCfg != nil { | |
| 936 | - | *cfg = *newCfg | |
| 937 | - | } | |
| 938 | - | callback() | |
| 939 | - | }) | |
| 940 | - | }, | |
| 941 | - | WatchConfig: func() error { | |
| 942 | - | return config.GetManager().Watch() | |
| 943 | - | }, | |
| 944 | 926 | LaunchExternalURL: desktop.LaunchExternalURL, | |
| 945 | 927 | LaunchBrowserURL: func(navCtx context.Context, uri string) error { | |
| 946 | 928 | return launchStandaloneBrowserURL(navCtx, browserLauncher.LaunchURL, uri) | |
| 947 | 929 | }, | |
| 948 | 930 | BrowserLaunchRelay: browserLaunchRelay, | |
| 949 | - | ConfigMigrator: config.NewMigrator(), | |
| 931 | + | MigrationChecker: config.NewMigrator(), | |
| 950 | 932 | HandlerDeps: *handlerDeps, | |
| 951 | 933 | } | |
| 952 | 934 | ||
| @@ -868,12 +868,8 @@ func buildUIDependencies( | |||||
| 868 | runtimeConfig := bootstrap.NewRuntimeConfigProvider(cfg, config.GetManager()) | 868 | runtimeConfig := bootstrap.NewRuntimeConfigProvider(cfg, config.GetManager()) | ||
| 869 | 869 | ||||
| 870 | uiDeps := &ui.Dependencies{ | 870 | uiDeps := &ui.Dependencies{ | ||
| 871 | - | Ctx: ctx, | 871 | + | Ctx: ctx, |
| 872 | - | Config: cfg, | 872 | + | RuntimeConfig: runtimeConfig, |
| 873 | - | RuntimeConfig: runtimeConfig, | |||
| 874 | - | EngineSettingsPayload: func() port.EngineSettingsPayload { | |||
| 875 | - | return bootstrap.EngineSettingsPayloadFromConfig(cfg) | |||
| 876 | - | }, | |||
| 877 | InitialURL: initialURL, | 873 | InitialURL: initialURL, | ||
| 878 | RestoreSessionID: restoreSessionID, | 874 | RestoreSessionID: restoreSessionID, | ||
| 879 | StartupCrashReports: startupCrashReports, | 875 | StartupCrashReports: startupCrashReports, | ||
| @@ -927,26 +923,12 @@ func buildUIDependencies( | |||||
| 927 | NewGTKEntryTarget: func(entry *gtk.SearchEntry) port.TextInputTarget { | 923 | NewGTKEntryTarget: func(entry *gtk.SearchEntry) port.TextInputTarget { | ||
| 928 | return textinput.NewGTKEntryTarget(entry) | 924 | return textinput.NewGTKEntryTarget(entry) | ||
| 929 | }, | 925 | }, | ||
| 930 | - | OnConfigChange: func(callback func()) { | |||
| 931 | - | config.GetManager().OnConfigChange(func(newCfg *config.Config) { | |||
| 932 | - | // No synchronization needed: viper fires this callback on the | |||
| 933 | - | // GTK main thread (via glib.IdleAdd), and all callers of cfg run | |||
| 934 | - | // on the same GTK main thread, so there is no concurrent access. | |||
| 935 | - | if cfg != nil && newCfg != nil { | |||
| 936 | - | *cfg = *newCfg | |||
| 937 | - | } | |||
| 938 | - | callback() | |||
| 939 | - | }) | |||
| 940 | - | }, | |||
| 941 | - | WatchConfig: func() error { | |||
| 942 | - | return config.GetManager().Watch() | |||
| 943 | - | }, | |||
| 944 | LaunchExternalURL: desktop.LaunchExternalURL, | 926 | LaunchExternalURL: desktop.LaunchExternalURL, | ||
| 945 | LaunchBrowserURL: func(navCtx context.Context, uri string) error { | 927 | LaunchBrowserURL: func(navCtx context.Context, uri string) error { | ||
| 946 | return launchStandaloneBrowserURL(navCtx, browserLauncher.LaunchURL, uri) | 928 | return launchStandaloneBrowserURL(navCtx, browserLauncher.LaunchURL, uri) | ||
| 947 | }, | 929 | }, | ||
| 948 | BrowserLaunchRelay: browserLaunchRelay, | 930 | BrowserLaunchRelay: browserLaunchRelay, | ||
| 949 | - | ConfigMigrator: config.NewMigrator(), | 931 | + | MigrationChecker: config.NewMigrator(), |
| 950 | HandlerDeps: *handlerDeps, | 932 | HandlerDeps: *handlerDeps, | ||
| 951 | } | 933 | } | ||
| 952 | 934 | ||||
M internal/ui/app.go +77 -79
| @@ -212,10 +212,7 @@ func New(deps *Dependencies) (*App, error) { | |||
| 212 | 212 | var clipboardOrchestrator port.ClipboardTextOrchestrator | |
| 213 | 213 | if deps.Clipboard != nil { | |
| 214 | 214 | autoCopyConfig = &autoCopyConfigFn{fn: func() bool { | |
| 215 | - | if deps.Config == nil { | |
| 216 | - | return false | |
| 217 | - | } | |
| 218 | - | return deps.Config.Clipboard.AutoCopyOnSelection | |
| 215 | + | return app.runtimeConfigSnapshot().UI.Clipboard.AutoCopyOnSelection | |
| 219 | 216 | }} | |
| 220 | 217 | clipboardOrchestrator = usecase.NewClipboardTextOrchestrator(deps.Clipboard, autoCopyConfig, func(textLen int) { | |
| 221 | 218 | cb := glib.SourceFunc(func(_ uintptr) bool { | |
| @@ -392,8 +389,8 @@ func (a *App) applyGTKColorSchemePreference(ctx context.Context) { | |||
| 392 | 389 | ||
| 393 | 390 | // Get the config color scheme preference (default follows system theme) | |
| 394 | 391 | scheme := "default" | |
| 395 | - | if a.deps != nil && a.deps.Config != nil && a.deps.Config.Appearance.ColorScheme != "" { | |
| 396 | - | scheme = a.deps.Config.Appearance.ColorScheme | |
| 392 | + | if runtimeScheme := a.runtimeConfigSnapshot().UI.Appearance.ColorScheme; runtimeScheme != "" { | |
| 393 | + | scheme = runtimeScheme | |
| 397 | 394 | } | |
| 398 | 395 | ||
| 399 | 396 | // Apply color scheme via libadwaita's StyleManager. | |
| @@ -488,7 +485,8 @@ func (a *App) createBrowserWindow(ctx context.Context, initialURL string) (*brow | |||
| 488 | 485 | return created, nil | |
| 489 | 486 | } | |
| 490 | 487 | ||
| 491 | - | mainWindow, err := window.New(ctx, a.gtkApp, a.deps.Config.Workspace.TabBarPosition) | |
| 488 | + | runtimeCfg := a.runtimeConfigSnapshot().UI | |
| 489 | + | mainWindow, err := window.New(ctx, a.gtkApp, runtimeCfg.Workspace.TabBarPosition) | |
| 492 | 490 | if err != nil { | |
| 493 | 491 | log.Warn().Err(err). | |
| 494 | 492 | Str("url_host", logging.SafeURLHost(initialURL)). | |
| @@ -515,9 +513,9 @@ func (a *App) createBrowserWindow(ctx context.Context, initialURL string) (*brow | |||
| 515 | 513 | ||
| 516 | 514 | // Create permission popup and dialog presenter | |
| 517 | 515 | if a.deps != nil && a.deps.PermissionUC != nil { | |
| 518 | - | uiScale := 1.0 | |
| 519 | - | if a.deps.Config != nil { | |
| 520 | - | uiScale = a.deps.Config.DefaultUIScale | |
| 516 | + | uiScale := runtimeCfg.DefaultUIScale | |
| 517 | + | if uiScale == 0 { | |
| 518 | + | uiScale = 1.0 | |
| 521 | 519 | } | |
| 522 | 520 | permPopup := component.NewPermissionPopup(nil, uiScale) | |
| 523 | 521 | if permPopup != nil { | |
| @@ -740,7 +738,7 @@ func (a *App) updateBrowserWindowTabBarVisibility(bw *browserWindow) { | |||
| 740 | 738 | if bw == nil || bw.mainWindow == nil || bw.mainWindow.TabBar() == nil { | |
| 741 | 739 | return | |
| 742 | 740 | } | |
| 743 | - | if a.deps == nil || a.deps.Config == nil || !a.deps.Config.Workspace.HideTabBarWhenSingleTab { | |
| 741 | + | if !a.runtimeConfigSnapshot().UI.Workspace.HideTabBarWhenSingleTab { | |
| 744 | 742 | bw.mainWindow.TabBar().SetAutoHidden(false) | |
| 745 | 743 | bw.mainWindow.SetTabBarContentInsetVisible(true) | |
| 746 | 744 | return | |
| @@ -1108,10 +1106,7 @@ func (a *App) initDownloadHandler(ctx context.Context) { | |||
| 1108 | 1106 | } | |
| 1109 | 1107 | ||
| 1110 | 1108 | // Determine download path from config, fallback to XDG. | |
| 1111 | - | downloadPath := "" | |
| 1112 | - | if a.deps.Config != nil { | |
| 1113 | - | downloadPath = a.deps.Config.Downloads.Path | |
| 1114 | - | } | |
| 1109 | + | downloadPath := a.runtimeConfigSnapshot().UI.Downloads.Path | |
| 1115 | 1110 | if downloadPath == "" && a.deps.XDG != nil { | |
| 1116 | 1111 | var err error | |
| 1117 | 1112 | downloadPath, err = a.deps.XDG.DownloadDir() | |
| @@ -1304,14 +1299,17 @@ func (a *App) initKeyboardHandler(ctx context.Context) { | |||
| 1304 | 1299 | func (a *App) initBrowserWindowInput(ctx context.Context, bw *browserWindow) { | |
| 1305 | 1300 | log := logging.FromContext(ctx) | |
| 1306 | 1301 | ||
| 1307 | - | if bw == nil || bw.mainWindow == nil || a.deps == nil || a.deps.Config == nil || a.kbDispatcher == nil { | |
| 1302 | + | if bw == nil || bw.mainWindow == nil || a.deps == nil || a.kbDispatcher == nil { | |
| 1308 | 1303 | return | |
| 1309 | 1304 | } | |
| 1310 | 1305 | if bw.keyboardHandler != nil || bw.globalShortcutHandler != nil { | |
| 1311 | 1306 | return | |
| 1312 | 1307 | } | |
| 1313 | 1308 | ||
| 1314 | - | bw.keyboardHandler = input.NewKeyboardHandler(ctx, &a.deps.Config.Workspace, &a.deps.Config.Session) | |
| 1309 | + | runtimeCfg := a.runtimeConfigSnapshot().UI | |
| 1310 | + | workspaceCfg := runtimeCfg.Workspace | |
| 1311 | + | sessionCfg := runtimeCfg.Session | |
| 1312 | + | bw.keyboardHandler = input.NewKeyboardHandler(ctx, &workspaceCfg, &sessionCfg) | |
| 1315 | 1313 | bw.keyboardHandler.SetOnAction(func(actionCtx context.Context, action input.Action) error { | |
| 1316 | 1314 | a.activateBrowserWindow(bw) | |
| 1317 | 1315 | if action == input.ActionClosePane { | |
| @@ -1361,8 +1359,8 @@ func (a *App) initBrowserWindowInput(ctx context.Context, bw *browserWindow) { | |||
| 1361 | 1359 | bw.globalShortcutHandler = input.NewGlobalShortcutHandler( | |
| 1362 | 1360 | ctx, | |
| 1363 | 1361 | bw.mainWindow.Window(), | |
| 1364 | - | &a.deps.Config.Workspace, | |
| 1365 | - | &a.deps.Config.Session, | |
| 1362 | + | &workspaceCfg, | |
| 1363 | + | &sessionCfg, | |
| 1366 | 1364 | bw.keyboardHandler, | |
| 1367 | 1365 | func(actionCtx context.Context, action input.Action) error { | |
| 1368 | 1366 | a.activateBrowserWindow(bw) | |
| @@ -1462,15 +1460,16 @@ type omniboxCallbacks struct { | |||
| 1462 | 1460 | ||
| 1463 | 1461 | func buildOmniboxConfig( | |
| 1464 | 1462 | deps *Dependencies, | |
| 1463 | + | runtimeCfg port.RuntimeUIConfig, | |
| 1465 | 1464 | faviconAdapter *adapter.FaviconAdapter, | |
| 1466 | 1465 | callbacks omniboxCallbacks, | |
| 1467 | 1466 | ) component.OmniboxConfig { | |
| 1468 | - | if deps == nil || deps.Config == nil { | |
| 1467 | + | if deps == nil { | |
| 1469 | 1468 | return component.OmniboxConfig{} | |
| 1470 | 1469 | } | |
| 1471 | 1470 | ||
| 1472 | - | shortcuts := make(map[string]usecase.SearchShortcut, len(deps.Config.SearchShortcuts)) | |
| 1473 | - | for key, shortcut := range deps.Config.SearchShortcuts { | |
| 1471 | + | shortcuts := make(map[string]usecase.SearchShortcut, len(runtimeCfg.SearchShortcuts)) | |
| 1472 | + | for key, shortcut := range runtimeCfg.SearchShortcuts { | |
| 1474 | 1473 | shortcuts[key] = usecase.SearchShortcut{ | |
| 1475 | 1474 | URL: shortcut.URL, | |
| 1476 | 1475 | Description: shortcut.Description, | |
| @@ -1483,11 +1482,11 @@ func buildOmniboxConfig( | |||
| 1483 | 1482 | FaviconAdapter: faviconAdapter, | |
| 1484 | 1483 | CopyURLUC: deps.CopyURLUC, | |
| 1485 | 1484 | ShortcutsUC: usecase.NewSearchShortcutsUseCase(shortcuts), | |
| 1486 | - | DefaultSearch: deps.Config.DefaultSearchEngine, | |
| 1487 | - | InitialBehavior: deps.Config.Omnibox.InitialBehavior, | |
| 1488 | - | MostVisitedDays: deps.Config.Omnibox.MostVisitedDays, | |
| 1485 | + | DefaultSearch: runtimeCfg.DefaultSearchEngine, | |
| 1486 | + | InitialBehavior: runtimeCfg.Omnibox.InitialBehavior, | |
| 1487 | + | MostVisitedDays: runtimeCfg.Omnibox.MostVisitedDays, | |
| 1489 | 1488 | SaveInitialBehavior: deps.HandlerDeps.SaveOmniboxInitialBehavior, | |
| 1490 | - | UIScale: deps.Config.DefaultUIScale, | |
| 1489 | + | UIScale: runtimeCfg.DefaultUIScale, | |
| 1491 | 1490 | OnNavigate: callbacks.OnNavigate, | |
| 1492 | 1491 | OnToast: callbacks.OnToast, | |
| 1493 | 1492 | OnFocusIn: callbacks.OnFocusIn, | |
| @@ -1516,7 +1515,11 @@ func NewStandaloneOmniboxRuntime( | |||
| 1516 | 1515 | registerFaviconInvalidator(deps.FaviconResolver, faviconAdapter) | |
| 1517 | 1516 | } | |
| 1518 | 1517 | ||
| 1519 | - | omniboxCfg := buildOmniboxConfig(deps, faviconAdapter, omniboxCallbacks{ | |
| 1518 | + | var runtimeCfg port.RuntimeUIConfig | |
| 1519 | + | if deps != nil && deps.RuntimeConfig != nil { | |
| 1520 | + | runtimeCfg = deps.RuntimeConfig.Current().UI | |
| 1521 | + | } | |
| 1522 | + | omniboxCfg := buildOmniboxConfig(deps, runtimeCfg, faviconAdapter, omniboxCallbacks{ | |
| 1520 | 1523 | OnNavigate: func(navCtx context.Context, url string) error { | |
| 1521 | 1524 | return handleStandaloneOmniboxNavigation(deps, navCtx, url) | |
| 1522 | 1525 | }, | |
| @@ -1554,11 +1557,11 @@ func handleStandaloneOmniboxNavigation(deps *Dependencies, ctx context.Context, | |||
| 1554 | 1557 | } | |
| 1555 | 1558 | ||
| 1556 | 1559 | func (a *App) initOmniboxConfig(ctx context.Context) { | |
| 1557 | - | if a.deps == nil || a.deps.Config == nil { | |
| 1560 | + | if a.deps == nil { | |
| 1558 | 1561 | return | |
| 1559 | 1562 | } | |
| 1560 | 1563 | ||
| 1561 | - | a.omniboxCfg = buildOmniboxConfig(a.deps, a.faviconAdapter, omniboxCallbacks{ | |
| 1564 | + | a.omniboxCfg = buildOmniboxConfig(a.deps, a.runtimeConfigSnapshot().UI, a.faviconAdapter, omniboxCallbacks{ | |
| 1562 | 1565 | OnNavigate: func(navCtx context.Context, url string) error { | |
| 1563 | 1566 | return a.navigateFromOmnibox(navCtx, url) | |
| 1564 | 1567 | }, | |
| @@ -1996,9 +1999,7 @@ func (a *App) switchToTargetTabIfConfigured( | |||
| 1996 | 1999 | } | |
| 1997 | 2000 | ||
| 1998 | 2001 | switchToTarget := false | |
| 1999 | - | if a.deps != nil && a.deps.Config != nil { | |
| 2000 | - | switchToTarget = a.deps.Config.Workspace.SwitchToTabOnMove | |
| 2001 | - | } | |
| 2002 | + | switchToTarget = a.runtimeConfigSnapshot().UI.Workspace.SwitchToTabOnMove | |
| 2002 | 2003 | if out.SourceTabClosed { | |
| 2003 | 2004 | switchToTarget = true | |
| 2004 | 2005 | } | |
| @@ -2035,8 +2036,8 @@ func (a *App) initSnapshotService(ctx context.Context) { | |||
| 2035 | 2036 | } | |
| 2036 | 2037 | ||
| 2037 | 2038 | intervalMs := 5000 // default | |
| 2038 | - | if a.deps.Config != nil && a.deps.Config.Session.SnapshotIntervalMs > 0 { | |
| 2039 | - | intervalMs = a.deps.Config.Session.SnapshotIntervalMs | |
| 2039 | + | if runtimeIntervalMs := a.runtimeConfigSnapshot().UI.Session.SnapshotIntervalMs; runtimeIntervalMs > 0 { | |
| 2040 | + | intervalMs = runtimeIntervalMs | |
| 2040 | 2041 | } | |
| 2041 | 2042 | ||
| 2042 | 2043 | a.snapshotService = a.deps.SnapshotServiceFactory(a, intervalMs) | |
| @@ -2062,14 +2063,15 @@ func (a *App) initUpdateCoordinator(ctx context.Context) { | |||
| 2062 | 2063 | return | |
| 2063 | 2064 | } | |
| 2064 | 2065 | ||
| 2066 | + | runtimeCfg := a.runtimeConfigSnapshot().UI | |
| 2065 | 2067 | a.updateCoord = coordinator.NewUpdateCoordinator( | |
| 2066 | 2068 | a.deps.CheckUpdateUC, | |
| 2067 | 2069 | a.deps.ApplyUpdateUC, | |
| 2068 | 2070 | func(ctx context.Context, msg string, level component.ToastLevel) { | |
| 2069 | 2071 | a.showToastOnLastFocusedBrowserWindow(ctx, msg, level) | |
| 2070 | 2072 | }, | |
| 2071 | - | a.deps.Config.Update.EnableOnStartup, | |
| 2072 | - | a.deps.Config.Update.AutoDownload, | |
| 2073 | + | runtimeCfg.Update.EnableOnStartup, | |
| 2074 | + | runtimeCfg.Update.AutoDownload, | |
| 2073 | 2075 | ) | |
| 2074 | 2076 | ||
| 2075 | 2077 | // Start async update check | |
| @@ -2603,13 +2605,14 @@ func (a *App) switchBrowserWindowTabIndex(ctx context.Context, bw *browserWindow | |||
| 2603 | 2605 | a.activateBrowserWindow(bw) | |
| 2604 | 2606 | return a.tabCoord.SwitchByIndex(ctx, target, index) | |
| 2605 | 2607 | } | |
| 2606 | - | if a.deps == nil || a.deps.Config == nil || a.deps.Config.Workspace.NewPaneURL == "" { | |
| 2608 | + | newPaneURL := a.runtimeConfigSnapshot().UI.Workspace.NewPaneURL | |
| 2609 | + | if newPaneURL == "" { | |
| 2607 | 2610 | logging.FromContext(ctx).Warn().Msg("switch tab index ignored: new pane URL is not configured") | |
| 2608 | 2611 | return fmt.Errorf("newPaneURL is not configured") | |
| 2609 | 2612 | } | |
| 2610 | 2613 | a.activateBrowserWindow(bw) | |
| 2611 | 2614 | ensureTarget := a.ensureTabTargetForBrowserWindow(bw) | |
| 2612 | - | _, err := a.tabCoord.Create(ctx, ensureTarget, urlutil.Normalize(a.deps.Config.Workspace.NewPaneURL)) | |
| 2615 | + | _, err := a.tabCoord.Create(ctx, ensureTarget, urlutil.Normalize(newPaneURL)) | |
| 2613 | 2616 | return err | |
| 2614 | 2617 | } | |
| 2615 | 2618 | ||
| @@ -3251,10 +3254,11 @@ func (a *App) initContentCoordinator( | |||
| 3251 | 3254 | ||
| 3252 | 3255 | // initTabCoordinator creates the TabCoordinator and wires all its callbacks. | |
| 3253 | 3256 | func (a *App) initTabCoordinator(ctx context.Context) { | |
| 3257 | + | runtimeCfg := a.runtimeConfigSnapshot().UI | |
| 3254 | 3258 | a.tabCoord = coordinator.NewTabCoordinator(ctx, coordinator.TabCoordinatorConfig{ | |
| 3255 | 3259 | TabsUC: a.tabsUC, | |
| 3256 | 3260 | MainWindow: a.mainWindow, | |
| 3257 | - | HideTabBarWhenSingleTab: a.deps.Config.Workspace.HideTabBarWhenSingleTab, | |
| 3261 | + | HideTabBarWhenSingleTab: runtimeCfg.Workspace.HideTabBarWhenSingleTab, | |
| 3258 | 3262 | }) | |
| 3259 | 3263 | a.tabCoord.SetOnTabCreated(func(ctx context.Context, target coordinator.TabTarget, tab *entity.Tab) { | |
| 3260 | 3264 | // Assign ownership from the callback target, not focus/global state. | |
| @@ -3359,6 +3363,7 @@ func (a *App) initCoordinators(ctx context.Context) { | |||
| 3359 | 3363 | }) | |
| 3360 | 3364 | ||
| 3361 | 3365 | // 3. Workspace Coordinator | |
| 3366 | + | runtimeCfg := a.runtimeConfigSnapshot().UI | |
| 3362 | 3367 | a.wsCoord = coordinator.NewWorkspaceCoordinator(ctx, coordinator.WorkspaceCoordinatorConfig{ | |
| 3363 | 3368 | PanesUC: a.panesUC, | |
| 3364 | 3369 | FocusMgr: a.focusMgr, | |
| @@ -3367,9 +3372,9 @@ func (a *App) initCoordinators(ctx context.Context) { | |||
| 3367 | 3372 | ContentCoord: a.contentCoord, | |
| 3368 | 3373 | GetActiveWS: getActiveWS, | |
| 3369 | 3374 | GenerateID: a.generateID, | |
| 3370 | - | NewPaneURL: a.deps.Config.Workspace.NewPaneURL, | |
| 3371 | - | ResizeStepPercent: a.deps.Config.Workspace.ResizeMode.StepPercent, | |
| 3372 | - | ResizeMinPanePercent: a.deps.Config.Workspace.ResizeMode.MinPanePercent, | |
| 3375 | + | NewPaneURL: runtimeCfg.Workspace.NewPaneURL, | |
| 3376 | + | ResizeStepPercent: runtimeCfg.Workspace.ResizeMode.StepPercent, | |
| 3377 | + | ResizeMinPanePercent: runtimeCfg.Workspace.ResizeMode.MinPanePercent, | |
| 3373 | 3378 | }) | |
| 3374 | 3379 | a.wsCoord.SetOnCloseLastPane(func(ctx context.Context) error { | |
| 3375 | 3380 | bw := a.lastFocusedBrowserWindow() | |
| @@ -3385,7 +3390,7 @@ func (a *App) initCoordinators(ctx context.Context) { | |||
| 3385 | 3390 | } | |
| 3386 | 3391 | a.contentCoord.SetPopupConfig( | |
| 3387 | 3392 | a.engine.Factory(), | |
| 3388 | - | &a.deps.Config.Workspace.BrowsingContexts, | |
| 3393 | + | a.popupBrowsingContextConfig(), | |
| 3389 | 3394 | a.generateID, | |
| 3390 | 3395 | ) | |
| 3391 | 3396 | a.contentCoord.SetPopupWindowIDResolver(func(paneID entity.PaneID) (string, bool) { | |
| @@ -3500,12 +3505,13 @@ func (a *App) initCoordinators(ctx context.Context) { | |||
| 3500 | 3505 | func (a *App) keyboardActions() dispatcher.KeyboardActions { | |
| 3501 | 3506 | return dispatcher.KeyboardActions{ | |
| 3502 | 3507 | NewTab: func(ctx context.Context) error { | |
| 3503 | - | if a.deps == nil || a.deps.Config == nil || a.deps.Config.Workspace.NewPaneURL == "" { | |
| 3508 | + | newPaneURL := a.runtimeConfigSnapshot().UI.Workspace.NewPaneURL | |
| 3509 | + | if newPaneURL == "" { | |
| 3504 | 3510 | logging.FromContext(ctx).Warn().Msg("new tab ignored: new pane URL is not configured") | |
| 3505 | 3511 | return fmt.Errorf("newPaneURL is not configured") | |
| 3506 | 3512 | } | |
| 3507 | 3513 | return a.withFocusedTabTarget(ctx, "new tab", true, func(target coordinator.TabTarget) error { | |
| 3508 | - | _, err := a.tabCoord.Create(ctx, target, urlutil.Normalize(a.deps.Config.Workspace.NewPaneURL)) | |
| 3514 | + | _, err := a.tabCoord.Create(ctx, target, urlutil.Normalize(newPaneURL)) | |
| 3509 | 3515 | return err | |
| 3510 | 3516 | }) | |
| 3511 | 3517 | }, | |
| @@ -3918,7 +3924,7 @@ func (a *App) updateModeIndicatorToaster(ctx context.Context, mode input.Mode) { | |||
| 3918 | 3924 | } | |
| 3919 | 3925 | ||
| 3920 | 3926 | // Check if mode indicator toaster is enabled in config. | |
| 3921 | - | if a.deps == nil || a.deps.Config == nil || !a.deps.Config.Workspace.Styling.ModeIndicatorToasterEnabled { | |
| 3927 | + | if !a.runtimeConfigSnapshot().UI.Workspace.Styling.ModeIndicatorToasterEnabled { | |
| 3922 | 3928 | bw.modeToaster.Hide() | |
| 3923 | 3929 | return | |
| 3924 | 3930 | } | |
| @@ -4083,9 +4089,7 @@ func (a *App) createWorkspaceViewWithoutAttach(ctx context.Context, tab *entity. | |||
| 4083 | 4089 | // Set find bar config for this workspace view | |
| 4084 | 4090 | wsView.SetFindBarConfig(a.findBarCfg) | |
| 4085 | 4091 | // Set auto-open omnibox on new pane | |
| 4086 | - | if a.deps.Config != nil { | |
| 4087 | - | wsView.SetAutoOpenOnNewPane(a.deps.Config.Omnibox.AutoOpenOnNewPane) | |
| 4088 | - | } | |
| 4092 | + | wsView.SetAutoOpenOnNewPane(a.runtimeConfigSnapshot().UI.Omnibox.AutoOpenOnNewPane) | |
| 4089 | 4093 | ||
| 4090 | 4094 | wsView.SetOnPaneFocused(func(paneID entity.PaneID) { | |
| 4091 | 4095 | if a.keyboardHandler != nil && a.keyboardHandler.Mode() == input.ModeResize { | |
| @@ -4442,18 +4446,12 @@ func (a *App) installFloatingOverlayPositioning(tabID entity.TabID, workspaceOve | |||
| 4442 | 4446 | ||
| 4443 | 4447 | // currentFloatingWidthPct returns the configured floating pane width percentage. | |
| 4444 | 4448 | func (a *App) currentFloatingWidthPct() float64 { | |
| 4445 | - | if a.deps != nil && a.deps.Config != nil { | |
| 4446 | - | return a.deps.Config.Workspace.FloatingPane.WidthPct | |
| 4447 | - | } | |
| 4448 | - | return 0 | |
| 4449 | + | return a.runtimeConfigSnapshot().UI.Workspace.FloatingPane.WidthPct | |
| 4449 | 4450 | } | |
| 4450 | 4451 | ||
| 4451 | 4452 | // currentFloatingHeightPct returns the configured floating pane height percentage. | |
| 4452 | 4453 | func (a *App) currentFloatingHeightPct() float64 { | |
| 4453 | - | if a.deps != nil && a.deps.Config != nil { | |
| 4454 | - | return a.deps.Config.Workspace.FloatingPane.HeightPct | |
| 4455 | - | } | |
| 4456 | - | return 0 | |
| 4454 | + | return a.runtimeConfigSnapshot().UI.Workspace.FloatingPane.HeightPct | |
| 4457 | 4455 | } | |
| 4458 | 4456 | ||
| 4459 | 4457 | func (a *App) ensureFloatingSession( | |
| @@ -5101,21 +5099,23 @@ func (a *App) initConfigWatcher(ctx context.Context) { | |||
| 5101 | 5099 | ||
| 5102 | 5100 | a.syncExternalThemeWatcher(ctx) | |
| 5103 | 5101 | ||
| 5104 | - | if a.deps.WatchConfig == nil || a.deps.OnConfigChange == nil { | |
| 5102 | + | if a.deps == nil || a.deps.RuntimeConfig == nil { | |
| 5105 | 5103 | log.Debug().Msg("no config watcher available, skipping config file watcher") | |
| 5106 | 5104 | return | |
| 5107 | 5105 | } | |
| 5108 | 5106 | ||
| 5109 | - | // Start viper watcher. | |
| 5110 | - | if err := a.deps.WatchConfig(); err != nil { | |
| 5107 | + | if err := a.deps.RuntimeConfig.Watch(); err != nil { | |
| 5111 | 5108 | log.Warn().Err(err).Msg("failed to start config watcher") | |
| 5112 | 5109 | return | |
| 5113 | 5110 | } | |
| 5114 | 5111 | ||
| 5115 | 5112 | // Hot-reload appearance and keybindings on config change. | |
| 5116 | - | // deps.Config is updated in-place before this callback fires. | |
| 5117 | - | a.deps.OnConfigChange(func() { | |
| 5113 | + | a.deps.RuntimeConfig.OnChange(func(snapshot port.RuntimeConfigSnapshot) { | |
| 5118 | 5114 | cb := glib.SourceFunc(func(_ uintptr) bool { | |
| 5115 | + | a.updateRuntimeConfig(snapshot) | |
| 5116 | + | runtimeCfg := snapshot.UI | |
| 5117 | + | workspaceCfg := runtimeCfg.Workspace | |
| 5118 | + | sessionCfg := runtimeCfg.Session | |
| 5119 | 5119 | a.syncExternalThemeWatcher(ctx) | |
| 5120 | 5120 | a.applyAppearanceConfig(ctx) | |
| 5121 | 5121 | for _, bw := range a.browserWindows { | |
| @@ -5126,10 +5126,10 @@ func (a *App) initConfigWatcher(ctx context.Context) { | |||
| 5126 | 5126 | // changes in the config file). | |
| 5127 | 5127 | bw.applySidebarWidthConfig(a) | |
| 5128 | 5128 | if bw.keyboardHandler != nil { | |
| 5129 | - | bw.keyboardHandler.ReloadShortcuts(ctx, &a.deps.Config.Workspace, &a.deps.Config.Session) | |
| 5129 | + | bw.keyboardHandler.ReloadShortcuts(ctx, &workspaceCfg, &sessionCfg) | |
| 5130 | 5130 | } | |
| 5131 | 5131 | if bw.globalShortcutHandler != nil { | |
| 5132 | - | bw.globalShortcutHandler.ReloadShortcuts(ctx, &a.deps.Config.Workspace, &a.deps.Config.Session) | |
| 5132 | + | bw.globalShortcutHandler.ReloadShortcuts(ctx, &workspaceCfg, &sessionCfg) | |
| 5133 | 5133 | } | |
| 5134 | 5134 | } | |
| 5135 | 5135 | return false | |
| @@ -5141,11 +5141,11 @@ func (a *App) initConfigWatcher(ctx context.Context) { | |||
| 5141 | 5141 | } | |
| 5142 | 5142 | ||
| 5143 | 5143 | func (a *App) syncExternalThemeWatcher(ctx context.Context) { | |
| 5144 | - | if a == nil || a.deps == nil || a.deps.Config == nil || a.deps.ExternalThemeWatcher == nil { | |
| 5144 | + | if a == nil || a.deps == nil || a.deps.ExternalThemeWatcher == nil { | |
| 5145 | 5145 | return | |
| 5146 | 5146 | } | |
| 5147 | 5147 | log := logging.FromContext(ctx) | |
| 5148 | - | cfg := a.deps.Config.Appearance.ExternalTheme | |
| 5148 | + | cfg := a.runtimeConfigSnapshot().UI.Appearance.ExternalTheme | |
| 5149 | 5149 | if err := a.deps.ExternalThemeWatcher.Start(ctx, cfg, func() { | |
| 5150 | 5150 | a.dispatchOnMainThread("ui.external_theme_reload", func() { | |
| 5151 | 5151 | a.applyAppearanceConfig(ctx) | |
| @@ -5157,18 +5157,14 @@ func (a *App) syncExternalThemeWatcher(ctx context.Context) { | |||
| 5157 | 5157 | ||
| 5158 | 5158 | func (a *App) applyAppearanceConfig(ctx context.Context) { | |
| 5159 | 5159 | log := logging.FromContext(ctx) | |
| 5160 | - | if a.deps == nil || a.deps.Config == nil { | |
| 5160 | + | if a == nil { | |
| 5161 | 5161 | return | |
| 5162 | 5162 | } | |
| 5163 | - | cfg := a.deps.Config | |
| 5163 | + | snapshot := a.runtimeConfigSnapshot() | |
| 5164 | 5164 | ||
| 5165 | 5165 | if a.engine != nil { | |
| 5166 | - | settings := port.EngineSettingsPayload{DefaultUIScale: cfg.DefaultUIScale} | |
| 5167 | - | if a.deps.EngineSettingsPayload != nil { | |
| 5168 | - | settings = a.deps.EngineSettingsPayload() | |
| 5169 | - | } | |
| 5170 | 5166 | if err := a.engine.UpdateSettings(ctx, port.EngineSettingsUpdate{ | |
| 5171 | - | Settings: settings, | |
| 5167 | + | Settings: snapshot.EngineSettings, | |
| 5172 | 5168 | }); err != nil { | |
| 5173 | 5169 | log.Warn().Err(err).Msg("failed to apply engine settings update") | |
| 5174 | 5170 | } | |
| @@ -5186,10 +5182,12 @@ func (a *App) applyAppearanceConfig(ctx context.Context) { | |||
| 5186 | 5182 | ||
| 5187 | 5183 | func (a *App) applyThemeAppearance(ctx context.Context) { | |
| 5188 | 5184 | log := logging.FromContext(ctx) | |
| 5189 | - | if a.deps == nil || a.deps.Config == nil || a.deps.Theme == nil { | |
| 5185 | + | if a.deps == nil || a.deps.Theme == nil { | |
| 5190 | 5186 | return | |
| 5191 | 5187 | } | |
| 5192 | - | cfg := a.deps.Config | |
| 5188 | + | runtimeCfg := a.runtimeConfigSnapshot().UI | |
| 5189 | + | appearanceCfg := runtimeCfg.Appearance | |
| 5190 | + | workspaceStyling := runtimeCfg.Workspace.Styling | |
| 5193 | 5191 | ||
| 5194 | 5192 | var display *gdk.Display | |
| 5195 | 5193 | if a.mainWindow != nil && a.mainWindow.Window() != nil { | |
| @@ -5201,16 +5199,16 @@ func (a *App) applyThemeAppearance(ctx context.Context) { | |||
| 5201 | 5199 | preference = a.deps.ColorResolver.Refresh() | |
| 5202 | 5200 | } | |
| 5203 | 5201 | if a.deps.ExternalThemeSource != nil { | |
| 5204 | - | a.deps.ExternalThemeSource.Configure(cfg.Appearance.ExternalTheme) | |
| 5202 | + | a.deps.ExternalThemeSource.Configure(appearanceCfg.ExternalTheme) | |
| 5205 | 5203 | } | |
| 5206 | 5204 | resolveThemeUC := a.deps.ResolveThemeUC | |
| 5207 | 5205 | if resolveThemeUC == nil { | |
| 5208 | 5206 | resolveThemeUC = usecase.NewResolveThemeUseCase(a.deps.ExternalThemeSource) | |
| 5209 | 5207 | } | |
| 5210 | 5208 | resolved, err := resolveThemeUC.Refresh(ctx, usecase.ResolveThemeInputFromConfig( | |
| 5211 | - | &cfg.Appearance, | |
| 5212 | - | cfg.DefaultUIScale, | |
| 5213 | - | &cfg.Workspace.Styling, | |
| 5209 | + | &appearanceCfg, | |
| 5210 | + | runtimeCfg.DefaultUIScale, | |
| 5211 | + | &workspaceStyling, | |
| 5214 | 5212 | preference, | |
| 5215 | 5213 | )) | |
| 5216 | 5214 | if err != nil { | |
| @@ -212,10 +212,7 @@ func New(deps *Dependencies) (*App, error) { | |||||
| 212 | var clipboardOrchestrator port.ClipboardTextOrchestrator | 212 | var clipboardOrchestrator port.ClipboardTextOrchestrator | ||
| 213 | if deps.Clipboard != nil { | 213 | if deps.Clipboard != nil { | ||
| 214 | autoCopyConfig = &autoCopyConfigFn{fn: func() bool { | 214 | autoCopyConfig = &autoCopyConfigFn{fn: func() bool { | ||
| 215 | - | if deps.Config == nil { | 215 | + | return app.runtimeConfigSnapshot().UI.Clipboard.AutoCopyOnSelection |
| 216 | - | return false | |||
| 217 | - | } | |||
| 218 | - | return deps.Config.Clipboard.AutoCopyOnSelection | |||
| 219 | }} | 216 | }} | ||
| 220 | clipboardOrchestrator = usecase.NewClipboardTextOrchestrator(deps.Clipboard, autoCopyConfig, func(textLen int) { | 217 | clipboardOrchestrator = usecase.NewClipboardTextOrchestrator(deps.Clipboard, autoCopyConfig, func(textLen int) { | ||
| 221 | cb := glib.SourceFunc(func(_ uintptr) bool { | 218 | cb := glib.SourceFunc(func(_ uintptr) bool { | ||
| @@ -392,8 +389,8 @@ func (a *App) applyGTKColorSchemePreference(ctx context.Context) { | |||||
| 392 | 389 | ||||
| 393 | // Get the config color scheme preference (default follows system theme) | 390 | // Get the config color scheme preference (default follows system theme) | ||
| 394 | scheme := "default" | 391 | scheme := "default" | ||
| 395 | - | if a.deps != nil && a.deps.Config != nil && a.deps.Config.Appearance.ColorScheme != "" { | 392 | + | if runtimeScheme := a.runtimeConfigSnapshot().UI.Appearance.ColorScheme; runtimeScheme != "" { |
| 396 | - | scheme = a.deps.Config.Appearance.ColorScheme | 393 | + | scheme = runtimeScheme |
| 397 | } | 394 | } | ||
| 398 | 395 | ||||
| 399 | // Apply color scheme via libadwaita's StyleManager. | 396 | // Apply color scheme via libadwaita's StyleManager. | ||
| @@ -488,7 +485,8 @@ func (a *App) createBrowserWindow(ctx context.Context, initialURL string) (*brow | |||||
| 488 | return created, nil | 485 | return created, nil | ||
| 489 | } | 486 | } | ||
| 490 | 487 | ||||
| 491 | - | mainWindow, err := window.New(ctx, a.gtkApp, a.deps.Config.Workspace.TabBarPosition) | 488 | + | runtimeCfg := a.runtimeConfigSnapshot().UI |
| 489 | + | mainWindow, err := window.New(ctx, a.gtkApp, runtimeCfg.Workspace.TabBarPosition) | |||
| 492 | if err != nil { | 490 | if err != nil { | ||
| 493 | log.Warn().Err(err). | 491 | log.Warn().Err(err). | ||
| 494 | Str("url_host", logging.SafeURLHost(initialURL)). | 492 | Str("url_host", logging.SafeURLHost(initialURL)). | ||
| @@ -515,9 +513,9 @@ func (a *App) createBrowserWindow(ctx context.Context, initialURL string) (*brow | |||||
| 515 | 513 | ||||
| 516 | // Create permission popup and dialog presenter | 514 | // Create permission popup and dialog presenter | ||
| 517 | if a.deps != nil && a.deps.PermissionUC != nil { | 515 | if a.deps != nil && a.deps.PermissionUC != nil { | ||
| 518 | - | uiScale := 1.0 | 516 | + | uiScale := runtimeCfg.DefaultUIScale |
| 519 | - | if a.deps.Config != nil { | 517 | + | if uiScale == 0 { |
| 520 | - | uiScale = a.deps.Config.DefaultUIScale | 518 | + | uiScale = 1.0 |
| 521 | } | 519 | } | ||
| 522 | permPopup := component.NewPermissionPopup(nil, uiScale) | 520 | permPopup := component.NewPermissionPopup(nil, uiScale) | ||
| 523 | if permPopup != nil { | 521 | if permPopup != nil { | ||
| @@ -740,7 +738,7 @@ func (a *App) updateBrowserWindowTabBarVisibility(bw *browserWindow) { | |||||
| 740 | if bw == nil || bw.mainWindow == nil || bw.mainWindow.TabBar() == nil { | 738 | if bw == nil || bw.mainWindow == nil || bw.mainWindow.TabBar() == nil { | ||
| 741 | return | 739 | return | ||
| 742 | } | 740 | } | ||
| 743 | - | if a.deps == nil || a.deps.Config == nil || !a.deps.Config.Workspace.HideTabBarWhenSingleTab { | 741 | + | if !a.runtimeConfigSnapshot().UI.Workspace.HideTabBarWhenSingleTab { |
| 744 | bw.mainWindow.TabBar().SetAutoHidden(false) | 742 | bw.mainWindow.TabBar().SetAutoHidden(false) | ||
| 745 | bw.mainWindow.SetTabBarContentInsetVisible(true) | 743 | bw.mainWindow.SetTabBarContentInsetVisible(true) | ||
| 746 | return | 744 | return | ||
| @@ -1108,10 +1106,7 @@ func (a *App) initDownloadHandler(ctx context.Context) { | |||||
| 1108 | } | 1106 | } | ||
| 1109 | 1107 | ||||
| 1110 | // Determine download path from config, fallback to XDG. | 1108 | // Determine download path from config, fallback to XDG. | ||
| 1111 | - | downloadPath := "" | 1109 | + | downloadPath := a.runtimeConfigSnapshot().UI.Downloads.Path |
| 1112 | - | if a.deps.Config != nil { | |||
| 1113 | - | downloadPath = a.deps.Config.Downloads.Path | |||
| 1114 | - | } | |||
| 1115 | if downloadPath == "" && a.deps.XDG != nil { | 1110 | if downloadPath == "" && a.deps.XDG != nil { | ||
| 1116 | var err error | 1111 | var err error | ||
| 1117 | downloadPath, err = a.deps.XDG.DownloadDir() | 1112 | downloadPath, err = a.deps.XDG.DownloadDir() | ||
| @@ -1304,14 +1299,17 @@ func (a *App) initKeyboardHandler(ctx context.Context) { | |||||
| 1304 | func (a *App) initBrowserWindowInput(ctx context.Context, bw *browserWindow) { | 1299 | func (a *App) initBrowserWindowInput(ctx context.Context, bw *browserWindow) { | ||
| 1305 | log := logging.FromContext(ctx) | 1300 | log := logging.FromContext(ctx) | ||
| 1306 | 1301 | ||||
| 1307 | - | if bw == nil || bw.mainWindow == nil || a.deps == nil || a.deps.Config == nil || a.kbDispatcher == nil { | 1302 | + | if bw == nil || bw.mainWindow == nil || a.deps == nil || a.kbDispatcher == nil { |
| 1308 | return | 1303 | return | ||
| 1309 | } | 1304 | } | ||
| 1310 | if bw.keyboardHandler != nil || bw.globalShortcutHandler != nil { | 1305 | if bw.keyboardHandler != nil || bw.globalShortcutHandler != nil { | ||
| 1311 | return | 1306 | return | ||
| 1312 | } | 1307 | } | ||
| 1313 | 1308 | ||||
| 1314 | - | bw.keyboardHandler = input.NewKeyboardHandler(ctx, &a.deps.Config.Workspace, &a.deps.Config.Session) | 1309 | + | runtimeCfg := a.runtimeConfigSnapshot().UI |
| 1310 | + | workspaceCfg := runtimeCfg.Workspace | |||
| 1311 | + | sessionCfg := runtimeCfg.Session | |||
| 1312 | + | bw.keyboardHandler = input.NewKeyboardHandler(ctx, &workspaceCfg, &sessionCfg) | |||
| 1315 | bw.keyboardHandler.SetOnAction(func(actionCtx context.Context, action input.Action) error { | 1313 | bw.keyboardHandler.SetOnAction(func(actionCtx context.Context, action input.Action) error { | ||
| 1316 | a.activateBrowserWindow(bw) | 1314 | a.activateBrowserWindow(bw) | ||
| 1317 | if action == input.ActionClosePane { | 1315 | if action == input.ActionClosePane { | ||
| @@ -1361,8 +1359,8 @@ func (a *App) initBrowserWindowInput(ctx context.Context, bw *browserWindow) { | |||||
| 1361 | bw.globalShortcutHandler = input.NewGlobalShortcutHandler( | 1359 | bw.globalShortcutHandler = input.NewGlobalShortcutHandler( | ||
| 1362 | ctx, | 1360 | ctx, | ||
| 1363 | bw.mainWindow.Window(), | 1361 | bw.mainWindow.Window(), | ||
| 1364 | - | &a.deps.Config.Workspace, | 1362 | + | &workspaceCfg, |
| 1365 | - | &a.deps.Config.Session, | 1363 | + | &sessionCfg, |
| 1366 | bw.keyboardHandler, | 1364 | bw.keyboardHandler, | ||
| 1367 | func(actionCtx context.Context, action input.Action) error { | 1365 | func(actionCtx context.Context, action input.Action) error { | ||
| 1368 | a.activateBrowserWindow(bw) | 1366 | a.activateBrowserWindow(bw) | ||
| @@ -1462,15 +1460,16 @@ type omniboxCallbacks struct { | |||||
| 1462 | 1460 | ||||
| 1463 | func buildOmniboxConfig( | 1461 | func buildOmniboxConfig( | ||
| 1464 | deps *Dependencies, | 1462 | deps *Dependencies, | ||
| 1463 | + | runtimeCfg port.RuntimeUIConfig, | |||
| 1465 | faviconAdapter *adapter.FaviconAdapter, | 1464 | faviconAdapter *adapter.FaviconAdapter, | ||
| 1466 | callbacks omniboxCallbacks, | 1465 | callbacks omniboxCallbacks, | ||
| 1467 | ) component.OmniboxConfig { | 1466 | ) component.OmniboxConfig { | ||
| 1468 | - | if deps == nil || deps.Config == nil { | 1467 | + | if deps == nil { |
| 1469 | return component.OmniboxConfig{} | 1468 | return component.OmniboxConfig{} | ||
| 1470 | } | 1469 | } | ||
| 1471 | 1470 | ||||
| 1472 | - | shortcuts := make(map[string]usecase.SearchShortcut, len(deps.Config.SearchShortcuts)) | 1471 | + | shortcuts := make(map[string]usecase.SearchShortcut, len(runtimeCfg.SearchShortcuts)) |
| 1473 | - | for key, shortcut := range deps.Config.SearchShortcuts { | 1472 | + | for key, shortcut := range runtimeCfg.SearchShortcuts { |
| 1474 | shortcuts[key] = usecase.SearchShortcut{ | 1473 | shortcuts[key] = usecase.SearchShortcut{ | ||
| 1475 | URL: shortcut.URL, | 1474 | URL: shortcut.URL, | ||
| 1476 | Description: shortcut.Description, | 1475 | Description: shortcut.Description, | ||
| @@ -1483,11 +1482,11 @@ func buildOmniboxConfig( | |||||
| 1483 | FaviconAdapter: faviconAdapter, | 1482 | FaviconAdapter: faviconAdapter, | ||
| 1484 | CopyURLUC: deps.CopyURLUC, | 1483 | CopyURLUC: deps.CopyURLUC, | ||
| 1485 | ShortcutsUC: usecase.NewSearchShortcutsUseCase(shortcuts), | 1484 | ShortcutsUC: usecase.NewSearchShortcutsUseCase(shortcuts), | ||
| 1486 | - | DefaultSearch: deps.Config.DefaultSearchEngine, | 1485 | + | DefaultSearch: runtimeCfg.DefaultSearchEngine, |
| 1487 | - | InitialBehavior: deps.Config.Omnibox.InitialBehavior, | 1486 | + | InitialBehavior: runtimeCfg.Omnibox.InitialBehavior, |
| 1488 | - | MostVisitedDays: deps.Config.Omnibox.MostVisitedDays, | 1487 | + | MostVisitedDays: runtimeCfg.Omnibox.MostVisitedDays, |
| 1489 | SaveInitialBehavior: deps.HandlerDeps.SaveOmniboxInitialBehavior, | 1488 | SaveInitialBehavior: deps.HandlerDeps.SaveOmniboxInitialBehavior, | ||
| 1490 | - | UIScale: deps.Config.DefaultUIScale, | 1489 | + | UIScale: runtimeCfg.DefaultUIScale, |
| 1491 | OnNavigate: callbacks.OnNavigate, | 1490 | OnNavigate: callbacks.OnNavigate, | ||
| 1492 | OnToast: callbacks.OnToast, | 1491 | OnToast: callbacks.OnToast, | ||
| 1493 | OnFocusIn: callbacks.OnFocusIn, | 1492 | OnFocusIn: callbacks.OnFocusIn, | ||
| @@ -1516,7 +1515,11 @@ func NewStandaloneOmniboxRuntime( | |||||
| 1516 | registerFaviconInvalidator(deps.FaviconResolver, faviconAdapter) | 1515 | registerFaviconInvalidator(deps.FaviconResolver, faviconAdapter) | ||
| 1517 | } | 1516 | } | ||
| 1518 | 1517 | ||||
| 1519 | - | omniboxCfg := buildOmniboxConfig(deps, faviconAdapter, omniboxCallbacks{ | 1518 | + | var runtimeCfg port.RuntimeUIConfig |
| 1519 | + | if deps != nil && deps.RuntimeConfig != nil { | |||
| 1520 | + | runtimeCfg = deps.RuntimeConfig.Current().UI | |||
| 1521 | + | } | |||
| 1522 | + | omniboxCfg := buildOmniboxConfig(deps, runtimeCfg, faviconAdapter, omniboxCallbacks{ | |||
| 1520 | OnNavigate: func(navCtx context.Context, url string) error { | 1523 | OnNavigate: func(navCtx context.Context, url string) error { | ||
| 1521 | return handleStandaloneOmniboxNavigation(deps, navCtx, url) | 1524 | return handleStandaloneOmniboxNavigation(deps, navCtx, url) | ||
| 1522 | }, | 1525 | }, | ||
| @@ -1554,11 +1557,11 @@ func handleStandaloneOmniboxNavigation(deps *Dependencies, ctx context.Context, | |||||
| 1554 | } | 1557 | } | ||
| 1555 | 1558 | ||||
| 1556 | func (a *App) initOmniboxConfig(ctx context.Context) { | 1559 | func (a *App) initOmniboxConfig(ctx context.Context) { | ||
| 1557 | - | if a.deps == nil || a.deps.Config == nil { | 1560 | + | if a.deps == nil { |
| 1558 | return | 1561 | return | ||
| 1559 | } | 1562 | } | ||
| 1560 | 1563 | ||||
| 1561 | - | a.omniboxCfg = buildOmniboxConfig(a.deps, a.faviconAdapter, omniboxCallbacks{ | 1564 | + | a.omniboxCfg = buildOmniboxConfig(a.deps, a.runtimeConfigSnapshot().UI, a.faviconAdapter, omniboxCallbacks{ |
| 1562 | OnNavigate: func(navCtx context.Context, url string) error { | 1565 | OnNavigate: func(navCtx context.Context, url string) error { | ||
| 1563 | return a.navigateFromOmnibox(navCtx, url) | 1566 | return a.navigateFromOmnibox(navCtx, url) | ||
| 1564 | }, | 1567 | }, | ||
| @@ -1996,9 +1999,7 @@ func (a *App) switchToTargetTabIfConfigured( | |||||
| 1996 | } | 1999 | } | ||
| 1997 | 2000 | ||||
| 1998 | switchToTarget := false | 2001 | switchToTarget := false | ||
| 1999 | - | if a.deps != nil && a.deps.Config != nil { | 2002 | + | switchToTarget = a.runtimeConfigSnapshot().UI.Workspace.SwitchToTabOnMove |
| 2000 | - | switchToTarget = a.deps.Config.Workspace.SwitchToTabOnMove | |||
| 2001 | - | } | |||
| 2002 | if out.SourceTabClosed { | 2003 | if out.SourceTabClosed { | ||
| 2003 | switchToTarget = true | 2004 | switchToTarget = true | ||
| 2004 | } | 2005 | } | ||
| @@ -2035,8 +2036,8 @@ func (a *App) initSnapshotService(ctx context.Context) { | |||||
| 2035 | } | 2036 | } | ||
| 2036 | 2037 | ||||
| 2037 | intervalMs := 5000 // default | 2038 | intervalMs := 5000 // default | ||
| 2038 | - | if a.deps.Config != nil && a.deps.Config.Session.SnapshotIntervalMs > 0 { | 2039 | + | if runtimeIntervalMs := a.runtimeConfigSnapshot().UI.Session.SnapshotIntervalMs; runtimeIntervalMs > 0 { |
| 2039 | - | intervalMs = a.deps.Config.Session.SnapshotIntervalMs | 2040 | + | intervalMs = runtimeIntervalMs |
| 2040 | } | 2041 | } | ||
| 2041 | 2042 | ||||
| 2042 | a.snapshotService = a.deps.SnapshotServiceFactory(a, intervalMs) | 2043 | a.snapshotService = a.deps.SnapshotServiceFactory(a, intervalMs) | ||
| @@ -2062,14 +2063,15 @@ func (a *App) initUpdateCoordinator(ctx context.Context) { | |||||
| 2062 | return | 2063 | return | ||
| 2063 | } | 2064 | } | ||
| 2064 | 2065 | ||||
| 2066 | + | runtimeCfg := a.runtimeConfigSnapshot().UI | |||
| 2065 | a.updateCoord = coordinator.NewUpdateCoordinator( | 2067 | a.updateCoord = coordinator.NewUpdateCoordinator( | ||
| 2066 | a.deps.CheckUpdateUC, | 2068 | a.deps.CheckUpdateUC, | ||
| 2067 | a.deps.ApplyUpdateUC, | 2069 | a.deps.ApplyUpdateUC, | ||
| 2068 | func(ctx context.Context, msg string, level component.ToastLevel) { | 2070 | func(ctx context.Context, msg string, level component.ToastLevel) { | ||
| 2069 | a.showToastOnLastFocusedBrowserWindow(ctx, msg, level) | 2071 | a.showToastOnLastFocusedBrowserWindow(ctx, msg, level) | ||
| 2070 | }, | 2072 | }, | ||
| 2071 | - | a.deps.Config.Update.EnableOnStartup, | 2073 | + | runtimeCfg.Update.EnableOnStartup, |
| 2072 | - | a.deps.Config.Update.AutoDownload, | 2074 | + | runtimeCfg.Update.AutoDownload, |
| 2073 | ) | 2075 | ) | ||
| 2074 | 2076 | ||||
| 2075 | // Start async update check | 2077 | // Start async update check | ||
| @@ -2603,13 +2605,14 @@ func (a *App) switchBrowserWindowTabIndex(ctx context.Context, bw *browserWindow | |||||
| 2603 | a.activateBrowserWindow(bw) | 2605 | a.activateBrowserWindow(bw) | ||
| 2604 | return a.tabCoord.SwitchByIndex(ctx, target, index) | 2606 | return a.tabCoord.SwitchByIndex(ctx, target, index) | ||
| 2605 | } | 2607 | } | ||
| 2606 | - | if a.deps == nil || a.deps.Config == nil || a.deps.Config.Workspace.NewPaneURL == "" { | 2608 | + | newPaneURL := a.runtimeConfigSnapshot().UI.Workspace.NewPaneURL |
| 2609 | + | if newPaneURL == "" { | |||
| 2607 | logging.FromContext(ctx).Warn().Msg("switch tab index ignored: new pane URL is not configured") | 2610 | logging.FromContext(ctx).Warn().Msg("switch tab index ignored: new pane URL is not configured") | ||
| 2608 | return fmt.Errorf("newPaneURL is not configured") | 2611 | return fmt.Errorf("newPaneURL is not configured") | ||
| 2609 | } | 2612 | } | ||
| 2610 | a.activateBrowserWindow(bw) | 2613 | a.activateBrowserWindow(bw) | ||
| 2611 | ensureTarget := a.ensureTabTargetForBrowserWindow(bw) | 2614 | ensureTarget := a.ensureTabTargetForBrowserWindow(bw) | ||
| 2612 | - | _, err := a.tabCoord.Create(ctx, ensureTarget, urlutil.Normalize(a.deps.Config.Workspace.NewPaneURL)) | 2615 | + | _, err := a.tabCoord.Create(ctx, ensureTarget, urlutil.Normalize(newPaneURL)) |
| 2613 | return err | 2616 | return err | ||
| 2614 | } | 2617 | } | ||
| 2615 | 2618 | ||||
| @@ -3251,10 +3254,11 @@ func (a *App) initContentCoordinator( | |||||
| 3251 | 3254 | ||||
| 3252 | // initTabCoordinator creates the TabCoordinator and wires all its callbacks. | 3255 | // initTabCoordinator creates the TabCoordinator and wires all its callbacks. | ||
| 3253 | func (a *App) initTabCoordinator(ctx context.Context) { | 3256 | func (a *App) initTabCoordinator(ctx context.Context) { | ||
| 3257 | + | runtimeCfg := a.runtimeConfigSnapshot().UI | |||
| 3254 | a.tabCoord = coordinator.NewTabCoordinator(ctx, coordinator.TabCoordinatorConfig{ | 3258 | a.tabCoord = coordinator.NewTabCoordinator(ctx, coordinator.TabCoordinatorConfig{ | ||
| 3255 | TabsUC: a.tabsUC, | 3259 | TabsUC: a.tabsUC, | ||
| 3256 | MainWindow: a.mainWindow, | 3260 | MainWindow: a.mainWindow, | ||
| 3257 | - | HideTabBarWhenSingleTab: a.deps.Config.Workspace.HideTabBarWhenSingleTab, | 3261 | + | HideTabBarWhenSingleTab: runtimeCfg.Workspace.HideTabBarWhenSingleTab, |
| 3258 | }) | 3262 | }) | ||
| 3259 | a.tabCoord.SetOnTabCreated(func(ctx context.Context, target coordinator.TabTarget, tab *entity.Tab) { | 3263 | a.tabCoord.SetOnTabCreated(func(ctx context.Context, target coordinator.TabTarget, tab *entity.Tab) { | ||
| 3260 | // Assign ownership from the callback target, not focus/global state. | 3264 | // Assign ownership from the callback target, not focus/global state. | ||
| @@ -3359,6 +3363,7 @@ func (a *App) initCoordinators(ctx context.Context) { | |||||
| 3359 | }) | 3363 | }) | ||
| 3360 | 3364 | ||||
| 3361 | // 3. Workspace Coordinator | 3365 | // 3. Workspace Coordinator | ||
| 3366 | + | runtimeCfg := a.runtimeConfigSnapshot().UI | |||
| 3362 | a.wsCoord = coordinator.NewWorkspaceCoordinator(ctx, coordinator.WorkspaceCoordinatorConfig{ | 3367 | a.wsCoord = coordinator.NewWorkspaceCoordinator(ctx, coordinator.WorkspaceCoordinatorConfig{ | ||
| 3363 | PanesUC: a.panesUC, | 3368 | PanesUC: a.panesUC, | ||
| 3364 | FocusMgr: a.focusMgr, | 3369 | FocusMgr: a.focusMgr, | ||
| @@ -3367,9 +3372,9 @@ func (a *App) initCoordinators(ctx context.Context) { | |||||
| 3367 | ContentCoord: a.contentCoord, | 3372 | ContentCoord: a.contentCoord, | ||
| 3368 | GetActiveWS: getActiveWS, | 3373 | GetActiveWS: getActiveWS, | ||
| 3369 | GenerateID: a.generateID, | 3374 | GenerateID: a.generateID, | ||
| 3370 | - | NewPaneURL: a.deps.Config.Workspace.NewPaneURL, | 3375 | + | NewPaneURL: runtimeCfg.Workspace.NewPaneURL, |
| 3371 | - | ResizeStepPercent: a.deps.Config.Workspace.ResizeMode.StepPercent, | 3376 | + | ResizeStepPercent: runtimeCfg.Workspace.ResizeMode.StepPercent, |
| 3372 | - | ResizeMinPanePercent: a.deps.Config.Workspace.ResizeMode.MinPanePercent, | 3377 | + | ResizeMinPanePercent: runtimeCfg.Workspace.ResizeMode.MinPanePercent, |
| 3373 | }) | 3378 | }) | ||
| 3374 | a.wsCoord.SetOnCloseLastPane(func(ctx context.Context) error { | 3379 | a.wsCoord.SetOnCloseLastPane(func(ctx context.Context) error { | ||
| 3375 | bw := a.lastFocusedBrowserWindow() | 3380 | bw := a.lastFocusedBrowserWindow() | ||
| @@ -3385,7 +3390,7 @@ func (a *App) initCoordinators(ctx context.Context) { | |||||
| 3385 | } | 3390 | } | ||
| 3386 | a.contentCoord.SetPopupConfig( | 3391 | a.contentCoord.SetPopupConfig( | ||
| 3387 | a.engine.Factory(), | 3392 | a.engine.Factory(), | ||
| 3388 | - | &a.deps.Config.Workspace.BrowsingContexts, | 3393 | + | a.popupBrowsingContextConfig(), |
| 3389 | a.generateID, | 3394 | a.generateID, | ||
| 3390 | ) | 3395 | ) | ||
| 3391 | a.contentCoord.SetPopupWindowIDResolver(func(paneID entity.PaneID) (string, bool) { | 3396 | a.contentCoord.SetPopupWindowIDResolver(func(paneID entity.PaneID) (string, bool) { | ||
| @@ -3500,12 +3505,13 @@ func (a *App) initCoordinators(ctx context.Context) { | |||||
| 3500 | func (a *App) keyboardActions() dispatcher.KeyboardActions { | 3505 | func (a *App) keyboardActions() dispatcher.KeyboardActions { | ||
| 3501 | return dispatcher.KeyboardActions{ | 3506 | return dispatcher.KeyboardActions{ | ||
| 3502 | NewTab: func(ctx context.Context) error { | 3507 | NewTab: func(ctx context.Context) error { | ||
| 3503 | - | if a.deps == nil || a.deps.Config == nil || a.deps.Config.Workspace.NewPaneURL == "" { | 3508 | + | newPaneURL := a.runtimeConfigSnapshot().UI.Workspace.NewPaneURL |
| 3509 | + | if newPaneURL == "" { | |||
| 3504 | logging.FromContext(ctx).Warn().Msg("new tab ignored: new pane URL is not configured") | 3510 | logging.FromContext(ctx).Warn().Msg("new tab ignored: new pane URL is not configured") | ||
| 3505 | return fmt.Errorf("newPaneURL is not configured") | 3511 | return fmt.Errorf("newPaneURL is not configured") | ||
| 3506 | } | 3512 | } | ||
| 3507 | return a.withFocusedTabTarget(ctx, "new tab", true, func(target coordinator.TabTarget) error { | 3513 | return a.withFocusedTabTarget(ctx, "new tab", true, func(target coordinator.TabTarget) error { | ||
| 3508 | - | _, err := a.tabCoord.Create(ctx, target, urlutil.Normalize(a.deps.Config.Workspace.NewPaneURL)) | 3514 | + | _, err := a.tabCoord.Create(ctx, target, urlutil.Normalize(newPaneURL)) |
| 3509 | return err | 3515 | return err | ||
| 3510 | }) | 3516 | }) | ||
| 3511 | }, | 3517 | }, | ||
| @@ -3918,7 +3924,7 @@ func (a *App) updateModeIndicatorToaster(ctx context.Context, mode input.Mode) { | |||||
| 3918 | } | 3924 | } | ||
| 3919 | 3925 | ||||
| 3920 | // Check if mode indicator toaster is enabled in config. | 3926 | // Check if mode indicator toaster is enabled in config. | ||
| 3921 | - | if a.deps == nil || a.deps.Config == nil || !a.deps.Config.Workspace.Styling.ModeIndicatorToasterEnabled { | 3927 | + | if !a.runtimeConfigSnapshot().UI.Workspace.Styling.ModeIndicatorToasterEnabled { |
| 3922 | bw.modeToaster.Hide() | 3928 | bw.modeToaster.Hide() | ||
| 3923 | return | 3929 | return | ||
| 3924 | } | 3930 | } | ||
| @@ -4083,9 +4089,7 @@ func (a *App) createWorkspaceViewWithoutAttach(ctx context.Context, tab *entity. | |||||
| 4083 | // Set find bar config for this workspace view | 4089 | // Set find bar config for this workspace view | ||
| 4084 | wsView.SetFindBarConfig(a.findBarCfg) | 4090 | wsView.SetFindBarConfig(a.findBarCfg) | ||
| 4085 | // Set auto-open omnibox on new pane | 4091 | // Set auto-open omnibox on new pane | ||
| 4086 | - | if a.deps.Config != nil { | 4092 | + | wsView.SetAutoOpenOnNewPane(a.runtimeConfigSnapshot().UI.Omnibox.AutoOpenOnNewPane) |
| 4087 | - | wsView.SetAutoOpenOnNewPane(a.deps.Config.Omnibox.AutoOpenOnNewPane) | |||
| 4088 | - | } | |||
| 4089 | 4093 | ||||
| 4090 | wsView.SetOnPaneFocused(func(paneID entity.PaneID) { | 4094 | wsView.SetOnPaneFocused(func(paneID entity.PaneID) { | ||
| 4091 | if a.keyboardHandler != nil && a.keyboardHandler.Mode() == input.ModeResize { | 4095 | if a.keyboardHandler != nil && a.keyboardHandler.Mode() == input.ModeResize { | ||
| @@ -4442,18 +4446,12 @@ func (a *App) installFloatingOverlayPositioning(tabID entity.TabID, workspaceOve | |||||
| 4442 | 4446 | ||||
| 4443 | // currentFloatingWidthPct returns the configured floating pane width percentage. | 4447 | // currentFloatingWidthPct returns the configured floating pane width percentage. | ||
| 4444 | func (a *App) currentFloatingWidthPct() float64 { | 4448 | func (a *App) currentFloatingWidthPct() float64 { | ||
| 4445 | - | if a.deps != nil && a.deps.Config != nil { | 4449 | + | return a.runtimeConfigSnapshot().UI.Workspace.FloatingPane.WidthPct |
| 4446 | - | return a.deps.Config.Workspace.FloatingPane.WidthPct | |||
| 4447 | - | } | |||
| 4448 | - | return 0 | |||
| 4449 | } | 4450 | } | ||
| 4450 | 4451 | ||||
| 4451 | // currentFloatingHeightPct returns the configured floating pane height percentage. | 4452 | // currentFloatingHeightPct returns the configured floating pane height percentage. | ||
| 4452 | func (a *App) currentFloatingHeightPct() float64 { | 4453 | func (a *App) currentFloatingHeightPct() float64 { | ||
| 4453 | - | if a.deps != nil && a.deps.Config != nil { | 4454 | + | return a.runtimeConfigSnapshot().UI.Workspace.FloatingPane.HeightPct |
| 4454 | - | return a.deps.Config.Workspace.FloatingPane.HeightPct | |||
| 4455 | - | } | |||
| 4456 | - | return 0 | |||
| 4457 | } | 4455 | } | ||
| 4458 | 4456 | ||||
| 4459 | func (a *App) ensureFloatingSession( | 4457 | func (a *App) ensureFloatingSession( | ||
| @@ -5101,21 +5099,23 @@ func (a *App) initConfigWatcher(ctx context.Context) { | |||||
| 5101 | 5099 | ||||
| 5102 | a.syncExternalThemeWatcher(ctx) | 5100 | a.syncExternalThemeWatcher(ctx) | ||
| 5103 | 5101 | ||||
| 5104 | - | if a.deps.WatchConfig == nil || a.deps.OnConfigChange == nil { | 5102 | + | if a.deps == nil || a.deps.RuntimeConfig == nil { |
| 5105 | log.Debug().Msg("no config watcher available, skipping config file watcher") | 5103 | log.Debug().Msg("no config watcher available, skipping config file watcher") | ||
| 5106 | return | 5104 | return | ||
| 5107 | } | 5105 | } | ||
| 5108 | 5106 | ||||
| 5109 | - | // Start viper watcher. | 5107 | + | if err := a.deps.RuntimeConfig.Watch(); err != nil { |
| 5110 | - | if err := a.deps.WatchConfig(); err != nil { | |||
| 5111 | log.Warn().Err(err).Msg("failed to start config watcher") | 5108 | log.Warn().Err(err).Msg("failed to start config watcher") | ||
| 5112 | return | 5109 | return | ||
| 5113 | } | 5110 | } | ||
| 5114 | 5111 | ||||
| 5115 | // Hot-reload appearance and keybindings on config change. | 5112 | // Hot-reload appearance and keybindings on config change. | ||
| 5116 | - | // deps.Config is updated in-place before this callback fires. | 5113 | + | a.deps.RuntimeConfig.OnChange(func(snapshot port.RuntimeConfigSnapshot) { |
| 5117 | - | a.deps.OnConfigChange(func() { | |||
| 5118 | cb := glib.SourceFunc(func(_ uintptr) bool { | 5114 | cb := glib.SourceFunc(func(_ uintptr) bool { | ||
| 5115 | + | a.updateRuntimeConfig(snapshot) | |||
| 5116 | + | runtimeCfg := snapshot.UI | |||
| 5117 | + | workspaceCfg := runtimeCfg.Workspace | |||
| 5118 | + | sessionCfg := runtimeCfg.Session | |||
| 5119 | a.syncExternalThemeWatcher(ctx) | 5119 | a.syncExternalThemeWatcher(ctx) | ||
| 5120 | a.applyAppearanceConfig(ctx) | 5120 | a.applyAppearanceConfig(ctx) | ||
| 5121 | for _, bw := range a.browserWindows { | 5121 | for _, bw := range a.browserWindows { | ||
| @@ -5126,10 +5126,10 @@ func (a *App) initConfigWatcher(ctx context.Context) { | |||||
| 5126 | // changes in the config file). | 5126 | // changes in the config file). | ||
| 5127 | bw.applySidebarWidthConfig(a) | 5127 | bw.applySidebarWidthConfig(a) | ||
| 5128 | if bw.keyboardHandler != nil { | 5128 | if bw.keyboardHandler != nil { | ||
| 5129 | - | bw.keyboardHandler.ReloadShortcuts(ctx, &a.deps.Config.Workspace, &a.deps.Config.Session) | 5129 | + | bw.keyboardHandler.ReloadShortcuts(ctx, &workspaceCfg, &sessionCfg) |
| 5130 | } | 5130 | } | ||
| 5131 | if bw.globalShortcutHandler != nil { | 5131 | if bw.globalShortcutHandler != nil { | ||
| 5132 | - | bw.globalShortcutHandler.ReloadShortcuts(ctx, &a.deps.Config.Workspace, &a.deps.Config.Session) | 5132 | + | bw.globalShortcutHandler.ReloadShortcuts(ctx, &workspaceCfg, &sessionCfg) |
| 5133 | } | 5133 | } | ||
| 5134 | } | 5134 | } | ||
| 5135 | return false | 5135 | return false | ||
| @@ -5141,11 +5141,11 @@ func (a *App) initConfigWatcher(ctx context.Context) { | |||||
| 5141 | } | 5141 | } | ||
| 5142 | 5142 | ||||
| 5143 | func (a *App) syncExternalThemeWatcher(ctx context.Context) { | 5143 | func (a *App) syncExternalThemeWatcher(ctx context.Context) { | ||
| 5144 | - | if a == nil || a.deps == nil || a.deps.Config == nil || a.deps.ExternalThemeWatcher == nil { | 5144 | + | if a == nil || a.deps == nil || a.deps.ExternalThemeWatcher == nil { |
| 5145 | return | 5145 | return | ||
| 5146 | } | 5146 | } | ||
| 5147 | log := logging.FromContext(ctx) | 5147 | log := logging.FromContext(ctx) | ||
| 5148 | - | cfg := a.deps.Config.Appearance.ExternalTheme | 5148 | + | cfg := a.runtimeConfigSnapshot().UI.Appearance.ExternalTheme |
| 5149 | if err := a.deps.ExternalThemeWatcher.Start(ctx, cfg, func() { | 5149 | if err := a.deps.ExternalThemeWatcher.Start(ctx, cfg, func() { | ||
| 5150 | a.dispatchOnMainThread("ui.external_theme_reload", func() { | 5150 | a.dispatchOnMainThread("ui.external_theme_reload", func() { | ||
| 5151 | a.applyAppearanceConfig(ctx) | 5151 | a.applyAppearanceConfig(ctx) | ||
| @@ -5157,18 +5157,14 @@ func (a *App) syncExternalThemeWatcher(ctx context.Context) { | |||||
| 5157 | 5157 | ||||
| 5158 | func (a *App) applyAppearanceConfig(ctx context.Context) { | 5158 | func (a *App) applyAppearanceConfig(ctx context.Context) { | ||
| 5159 | log := logging.FromContext(ctx) | 5159 | log := logging.FromContext(ctx) | ||
| 5160 | - | if a.deps == nil || a.deps.Config == nil { | 5160 | + | if a == nil { |
| 5161 | return | 5161 | return | ||
| 5162 | } | 5162 | } | ||
| 5163 | - | cfg := a.deps.Config | 5163 | + | snapshot := a.runtimeConfigSnapshot() |
| 5164 | 5164 | ||||
| 5165 | if a.engine != nil { | 5165 | if a.engine != nil { | ||
| 5166 | - | settings := port.EngineSettingsPayload{DefaultUIScale: cfg.DefaultUIScale} | |||
| 5167 | - | if a.deps.EngineSettingsPayload != nil { | |||
| 5168 | - | settings = a.deps.EngineSettingsPayload() | |||
| 5169 | - | } | |||
| 5170 | if err := a.engine.UpdateSettings(ctx, port.EngineSettingsUpdate{ | 5166 | if err := a.engine.UpdateSettings(ctx, port.EngineSettingsUpdate{ | ||
| 5171 | - | Settings: settings, | 5167 | + | Settings: snapshot.EngineSettings, |
| 5172 | }); err != nil { | 5168 | }); err != nil { | ||
| 5173 | log.Warn().Err(err).Msg("failed to apply engine settings update") | 5169 | log.Warn().Err(err).Msg("failed to apply engine settings update") | ||
| 5174 | } | 5170 | } | ||
| @@ -5186,10 +5182,12 @@ func (a *App) applyAppearanceConfig(ctx context.Context) { | |||||
| 5186 | 5182 | ||||
| 5187 | func (a *App) applyThemeAppearance(ctx context.Context) { | 5183 | func (a *App) applyThemeAppearance(ctx context.Context) { | ||
| 5188 | log := logging.FromContext(ctx) | 5184 | log := logging.FromContext(ctx) | ||
| 5189 | - | if a.deps == nil || a.deps.Config == nil || a.deps.Theme == nil { | 5185 | + | if a.deps == nil || a.deps.Theme == nil { |
| 5190 | return | 5186 | return | ||
| 5191 | } | 5187 | } | ||
| 5192 | - | cfg := a.deps.Config | 5188 | + | runtimeCfg := a.runtimeConfigSnapshot().UI |
| 5189 | + | appearanceCfg := runtimeCfg.Appearance | |||
| 5190 | + | workspaceStyling := runtimeCfg.Workspace.Styling | |||
| 5193 | 5191 | ||||
| 5194 | var display *gdk.Display | 5192 | var display *gdk.Display | ||
| 5195 | if a.mainWindow != nil && a.mainWindow.Window() != nil { | 5193 | if a.mainWindow != nil && a.mainWindow.Window() != nil { | ||
| @@ -5201,16 +5199,16 @@ func (a *App) applyThemeAppearance(ctx context.Context) { | |||||
| 5201 | preference = a.deps.ColorResolver.Refresh() | 5199 | preference = a.deps.ColorResolver.Refresh() | ||
| 5202 | } | 5200 | } | ||
| 5203 | if a.deps.ExternalThemeSource != nil { | 5201 | if a.deps.ExternalThemeSource != nil { | ||
| 5204 | - | a.deps.ExternalThemeSource.Configure(cfg.Appearance.ExternalTheme) | 5202 | + | a.deps.ExternalThemeSource.Configure(appearanceCfg.ExternalTheme) |
| 5205 | } | 5203 | } | ||
| 5206 | resolveThemeUC := a.deps.ResolveThemeUC | 5204 | resolveThemeUC := a.deps.ResolveThemeUC | ||
| 5207 | if resolveThemeUC == nil { | 5205 | if resolveThemeUC == nil { | ||
| 5208 | resolveThemeUC = usecase.NewResolveThemeUseCase(a.deps.ExternalThemeSource) | 5206 | resolveThemeUC = usecase.NewResolveThemeUseCase(a.deps.ExternalThemeSource) | ||
| 5209 | } | 5207 | } | ||
| 5210 | resolved, err := resolveThemeUC.Refresh(ctx, usecase.ResolveThemeInputFromConfig( | 5208 | resolved, err := resolveThemeUC.Refresh(ctx, usecase.ResolveThemeInputFromConfig( | ||
| 5211 | - | &cfg.Appearance, | 5209 | + | &appearanceCfg, |
| 5212 | - | cfg.DefaultUIScale, | 5210 | + | runtimeCfg.DefaultUIScale, |
| 5213 | - | &cfg.Workspace.Styling, | 5211 | + | &workspaceStyling, |
| 5214 | preference, | 5212 | preference, | ||
| 5215 | )) | 5213 | )) | ||
| 5216 | if err != nil { | 5214 | if err != nil { | ||
M internal/ui/app_browser_launch_test.go +128 -84
| @@ -1016,13 +1016,15 @@ func TestApp_CheckConfigMigrationUsesLastFocusedBrowserWindowToaster(t *testing. | |||
| 1016 | 1016 | migrator.EXPECT().CheckMigration().Return(&port.MigrationResult{MissingKeys: []string{"update.notify_on_new_settings"}}, nil).Once() | |
| 1017 | 1017 | ||
| 1018 | 1018 | bw := &browserWindow{id: "window-1", appToaster: toaster} | |
| 1019 | + | cfg := &config.Config{ | |
| 1020 | + | Update: config.UpdateConfig{NotifyOnNewSettings: true}, | |
| 1021 | + | } | |
| 1019 | 1022 | app := &App{ | |
| 1020 | 1023 | deps: &Dependencies{ | |
| 1021 | - | Config: &config.Config{ | |
| 1022 | - | Update: config.UpdateConfig{NotifyOnNewSettings: true}, | |
| 1023 | - | }, | |
| 1024 | - | ConfigMigrator: migrator, | |
| 1024 | + | MigrationChecker: migrator, | |
| 1025 | 1025 | }, | |
| 1026 | + | runtimeConfig: runtimeConfigSnapshotForTest(cfg), | |
| 1027 | + | runtimeConfigLoaded: true, | |
| 1026 | 1028 | browserWindows: map[string]*browserWindow{bw.id: bw}, | |
| 1027 | 1029 | lastFocusedWindowID: bw.id, | |
| 1028 | 1030 | } | |
| @@ -1132,9 +1134,10 @@ func TestApp_RestoreSessionHandlesEmptyWindowSnapshotAsSuccess(t *testing.T) { | |||
| 1132 | 1134 | ||
| 1133 | 1135 | app := &App{ | |
| 1134 | 1136 | deps: &Dependencies{ | |
| 1135 | - | Config: &config.Config{}, | |
| 1136 | 1137 | SessionStateRepo: mockSessionStateRepoWithSnapshot(t, sessionID, state), | |
| 1137 | 1138 | }, | |
| 1139 | + | runtimeConfig: runtimeConfigSnapshotForTest(&config.Config{}), | |
| 1140 | + | runtimeConfigLoaded: true, | |
| 1138 | 1141 | tabs: entity.NewTabList(), | |
| 1139 | 1142 | browserWindows: map[string]*browserWindow{staleWindow.id: staleWindow}, | |
| 1140 | 1143 | lastFocusedWindowID: staleWindow.id, | |
| @@ -1171,11 +1174,12 @@ func TestApp_CreateInitialTabNoFallbackAfterEmptyWindowRestore(t *testing.T) { | |||
| 1171 | 1174 | bw := &browserWindow{id: "window-1", tabs: windowTabs} | |
| 1172 | 1175 | app := &App{ | |
| 1173 | 1176 | deps: &Dependencies{ | |
| 1174 | - | Config: &config.Config{}, | |
| 1175 | 1177 | RestoreSessionID: string(sessionID), | |
| 1176 | 1178 | InitialURL: "https://fallback.example", | |
| 1177 | 1179 | SessionStateRepo: mockSessionStateRepoWithSnapshot(t, sessionID, state), | |
| 1178 | 1180 | }, | |
| 1181 | + | runtimeConfig: runtimeConfigSnapshotForTest(&config.Config{}), | |
| 1182 | + | runtimeConfigLoaded: true, | |
| 1179 | 1183 | tabs: entity.NewTabList(), | |
| 1180 | 1184 | tabsUC: tabsUC, | |
| 1181 | 1185 | tabCoord: coordinator.NewTabCoordinator(ctx, coordinator.TabCoordinatorConfig{TabsUC: tabsUC, Tabs: entity.NewTabList()}), | |
| @@ -1213,15 +1217,16 @@ func TestApp_RestoreSessionClearsStaleUIStateBeforeApplyingRestoredTabs(t *testi | |||
| 1213 | 1217 | ||
| 1214 | 1218 | app := &App{ | |
| 1215 | 1219 | deps: &Dependencies{ | |
| 1216 | - | Config: &config.Config{}, | |
| 1217 | 1220 | SessionStateRepo: mockSessionStateRepoWithSnapshot(t, restoredSessionID, entity.SnapshotFromTabList(restoredSessionID, restoredTabs)), | |
| 1218 | 1221 | }, | |
| 1219 | - | mainWindow: mainWindow, | |
| 1220 | - | widgetFactory: layout.NewGtkWidgetFactory(), | |
| 1221 | - | tabs: entity.NewTabList(), | |
| 1222 | - | browserWindows: map[string]*browserWindow{staleWindow.id: staleWindow}, | |
| 1223 | - | workspaceViews: map[entity.TabID]*component.WorkspaceView{staleTab.ID: &component.WorkspaceView{}}, | |
| 1224 | - | windowForTab: map[entity.TabID]*browserWindow{staleTab.ID: staleWindow}, | |
| 1222 | + | runtimeConfig: runtimeConfigSnapshotForTest(&config.Config{}), | |
| 1223 | + | runtimeConfigLoaded: true, | |
| 1224 | + | mainWindow: mainWindow, | |
| 1225 | + | widgetFactory: layout.NewGtkWidgetFactory(), | |
| 1226 | + | tabs: entity.NewTabList(), | |
| 1227 | + | browserWindows: map[string]*browserWindow{staleWindow.id: staleWindow}, | |
| 1228 | + | workspaceViews: map[entity.TabID]*component.WorkspaceView{staleTab.ID: &component.WorkspaceView{}}, | |
| 1229 | + | windowForTab: map[entity.TabID]*browserWindow{staleTab.ID: staleWindow}, | |
| 1225 | 1230 | floatingSessions: map[floatingSessionKey]*floatingWorkspaceSession{ | |
| 1226 | 1231 | staleSessionKey: {}, | |
| 1227 | 1232 | }, | |
| @@ -1280,17 +1285,18 @@ func TestApp_RestoreSessionWiresStackedPaneTitleBarCallbacks(t *testing.T) { | |||
| 1280 | 1285 | ||
| 1281 | 1286 | app := &App{ | |
| 1282 | 1287 | deps: &Dependencies{ | |
| 1283 | - | Config: &config.Config{}, | |
| 1284 | 1288 | SessionStateRepo: mockSessionStateRepoWithSnapshot(t, stackedSessionID, entity.SnapshotFromTabList(stackedSessionID, stackedTabs)), | |
| 1285 | 1289 | }, | |
| 1286 | - | mainWindow: mainWindow, | |
| 1287 | - | widgetFactory: layout.NewGtkWidgetFactory(), | |
| 1288 | - | contentCoord: &contentcoord.Coordinator{}, | |
| 1289 | - | wsCoord: coordinator.NewWorkspaceCoordinator(context.Background(), coordinator.WorkspaceCoordinatorConfig{ContentCoord: &contentcoord.Coordinator{}}), | |
| 1290 | - | tabs: entity.NewTabList(), | |
| 1291 | - | browserWindows: map[string]*browserWindow{}, | |
| 1292 | - | workspaceViews: map[entity.TabID]*component.WorkspaceView{}, | |
| 1293 | - | windowForTab: map[entity.TabID]*browserWindow{}, | |
| 1290 | + | runtimeConfig: runtimeConfigSnapshotForTest(&config.Config{}), | |
| 1291 | + | runtimeConfigLoaded: true, | |
| 1292 | + | mainWindow: mainWindow, | |
| 1293 | + | widgetFactory: layout.NewGtkWidgetFactory(), | |
| 1294 | + | contentCoord: &contentcoord.Coordinator{}, | |
| 1295 | + | wsCoord: coordinator.NewWorkspaceCoordinator(context.Background(), coordinator.WorkspaceCoordinatorConfig{ContentCoord: &contentcoord.Coordinator{}}), | |
| 1296 | + | tabs: entity.NewTabList(), | |
| 1297 | + | browserWindows: map[string]*browserWindow{}, | |
| 1298 | + | workspaceViews: map[entity.TabID]*component.WorkspaceView{}, | |
| 1299 | + | windowForTab: map[entity.TabID]*browserWindow{}, | |
| 1294 | 1300 | } | |
| 1295 | 1301 | ||
| 1296 | 1302 | if err := app.restoreSession(context.Background(), stackedSessionID); err != nil { | |
| @@ -1767,7 +1773,9 @@ func TestApp_MoveActivePaneToTabFromBrowserWindowAnchorsOwningWindow(t *testing. | |||
| 1767 | 1773 | app := &App{ | |
| 1768 | 1774 | tabs: tabs, | |
| 1769 | 1775 | movePaneToTabUC: usecase.NewMovePaneToTabUseCase(func() string { return "new-tab" }), | |
| 1770 | - | deps: &Dependencies{Config: &config.Config{}}, | |
| 1776 | + | deps: &Dependencies{}, | |
| 1777 | + | runtimeConfig: runtimeConfigSnapshotForTest(&config.Config{}), | |
| 1778 | + | runtimeConfigLoaded: true, | |
| 1771 | 1779 | browserWindows: map[string]*browserWindow{first.id: first, second.id: second}, | |
| 1772 | 1780 | lastFocusedWindowID: second.id, | |
| 1773 | 1781 | } | |
| @@ -1804,7 +1812,9 @@ func TestApp_MoveActivePaneToTabFromBrowserWindowActivatesTargetOwnerForCrossWin | |||
| 1804 | 1812 | app := &App{ | |
| 1805 | 1813 | tabs: tabs, | |
| 1806 | 1814 | movePaneToTabUC: usecase.NewMovePaneToTabUseCase(func() string { return "generated" }), | |
| 1807 | - | deps: &Dependencies{Config: &config.Config{}}, | |
| 1815 | + | deps: &Dependencies{}, | |
| 1816 | + | runtimeConfig: runtimeConfigSnapshotForTest(&config.Config{}), | |
| 1817 | + | runtimeConfigLoaded: true, | |
| 1808 | 1818 | browserWindows: map[string]*browserWindow{first.id: first, second.id: second}, | |
| 1809 | 1819 | windowForTab: map[entity.TabID]*browserWindow{tab1.ID: first, tab2.ID: second, tab3.ID: second}, | |
| 1810 | 1820 | lastFocusedWindowID: first.id, | |
| @@ -1859,7 +1869,9 @@ func TestApp_MoveActivePaneToTabFromBrowserWindowCrossWindowDerivedMirror(t *tes | |||
| 1859 | 1869 | app := &App{ | |
| 1860 | 1870 | tabs: tabs, | |
| 1861 | 1871 | movePaneToTabUC: usecase.NewMovePaneToTabUseCase(func() string { return "generated" }), | |
| 1862 | - | deps: &Dependencies{Config: &config.Config{}}, | |
| 1872 | + | deps: &Dependencies{}, | |
| 1873 | + | runtimeConfig: runtimeConfigSnapshotForTest(&config.Config{}), | |
| 1874 | + | runtimeConfigLoaded: true, | |
| 1863 | 1875 | browserWindows: map[string]*browserWindow{first.id: first, second.id: second}, | |
| 1864 | 1876 | windowForTab: map[entity.TabID]*browserWindow{tab1.ID: first, tab3.ID: second}, | |
| 1865 | 1877 | lastFocusedWindowID: first.id, | |
| @@ -1909,10 +1921,12 @@ func TestApp_InitKeyboardHandlerDoesNotReattachExistingWindowInput(t *testing.T) | |||
| 1909 | 1921 | globalShortcutHandler: sentinelShortcutHandler, | |
| 1910 | 1922 | } | |
| 1911 | 1923 | app := &App{ | |
| 1912 | - | deps: &Dependencies{Config: &config.Config{}}, | |
| 1913 | - | mainWindow: mainWindow, | |
| 1914 | - | browserWindows: map[string]*browserWindow{bw.id: bw}, | |
| 1915 | - | kbDispatcher: dispatcher.NewKeyboardDispatcher(context.Background(), nil, nil, nil, nil, dispatcher.KeyboardActions{}, func(context.Context) entity.PaneID { return "" }), | |
| 1924 | + | deps: &Dependencies{}, | |
| 1925 | + | runtimeConfig: runtimeConfigSnapshotForTest(&config.Config{}), | |
| 1926 | + | runtimeConfigLoaded: true, | |
| 1927 | + | mainWindow: mainWindow, | |
| 1928 | + | browserWindows: map[string]*browserWindow{bw.id: bw}, | |
| 1929 | + | kbDispatcher: dispatcher.NewKeyboardDispatcher(context.Background(), nil, nil, nil, nil, dispatcher.KeyboardActions{}, func(context.Context) entity.PaneID { return "" }), | |
| 1916 | 1930 | } | |
| 1917 | 1931 | ||
| 1918 | 1932 | app.initKeyboardHandler(context.Background()) | |
| @@ -1961,10 +1975,13 @@ func TestApp_UpdateBrowserWindowTabBarVisibilityAutoHidesSingleTabButPreservesAl | |||
| 1961 | 1975 | } | |
| 1962 | 1976 | setWindowTabBar(t, mainWindow, tabBar) | |
| 1963 | 1977 | bw := &browserWindow{id: "window-1", mainWindow: mainWindow} | |
| 1978 | + | cfg := &config.Config{} | |
| 1979 | + | cfg.Workspace.HideTabBarWhenSingleTab = true | |
| 1964 | 1980 | app := &App{ | |
| 1965 | - | deps: &Dependencies{Config: &config.Config{}}, | |
| 1981 | + | deps: &Dependencies{}, | |
| 1982 | + | runtimeConfig: runtimeConfigSnapshotForTest(cfg), | |
| 1983 | + | runtimeConfigLoaded: true, | |
| 1966 | 1984 | } | |
| 1967 | - | app.deps.Config.Workspace.HideTabBarWhenSingleTab = true | |
| 1968 | 1985 | ||
| 1969 | 1986 | tab1 := entity.NewTab(entity.TabID("tab-1"), entity.WorkspaceID("workspace-1"), entity.NewPane(entity.PaneID("pane-1"))) | |
| 1970 | 1987 | tab2 := entity.NewTab(entity.TabID("tab-2"), entity.WorkspaceID("workspace-2"), entity.NewPane(entity.PaneID("pane-2"))) | |
| @@ -2031,10 +2048,13 @@ func TestApp_UpdateBrowserWindowTabBarVisibilityHonorsHideWhenSingleTabDisabled( | |||
| 2031 | 2048 | } | |
| 2032 | 2049 | setWindowTabBar(t, mainWindow, tabBar) | |
| 2033 | 2050 | bw := &browserWindow{id: "window-1", mainWindow: mainWindow} | |
| 2051 | + | cfg := &config.Config{} | |
| 2052 | + | cfg.Workspace.HideTabBarWhenSingleTab = false | |
| 2034 | 2053 | app := &App{ | |
| 2035 | - | deps: &Dependencies{Config: &config.Config{}}, | |
| 2054 | + | deps: &Dependencies{}, | |
| 2055 | + | runtimeConfig: runtimeConfigSnapshotForTest(cfg), | |
| 2056 | + | runtimeConfigLoaded: true, | |
| 2036 | 2057 | } | |
| 2037 | - | app.deps.Config.Workspace.HideTabBarWhenSingleTab = false | |
| 2038 | 2058 | ||
| 2039 | 2059 | app.updateBrowserWindowTabBarVisibility(bw) | |
| 2040 | 2060 | ||
| @@ -2064,8 +2084,9 @@ func TestApp_BuildRestoredWindowUIUpdatesEmptyWindowTabBarVisibility(t *testing. | |||
| 2064 | 2084 | ||
| 2065 | 2085 | mw.SetTabBarContentInsetVisible(true) | |
| 2066 | 2086 | bw := &browserWindow{id: "window-1", mainWindow: mw, tabs: entity.NewTabList()} | |
| 2067 | - | app := &App{deps: &Dependencies{Config: &config.Config{}}} | |
| 2068 | - | app.deps.Config.Workspace.HideTabBarWhenSingleTab = true | |
| 2087 | + | cfg := &config.Config{} | |
| 2088 | + | cfg.Workspace.HideTabBarWhenSingleTab = true | |
| 2089 | + | app := appWithRuntimeConfigForTest(cfg) | |
| 2069 | 2090 | ||
| 2070 | 2091 | app.buildRestoredWindowUI(context.Background(), []*browserWindow{bw}) | |
| 2071 | 2092 | ||
| @@ -2087,10 +2108,13 @@ func TestApp_UpdateBrowserWindowTabBarVisibility_BottomInset(t *testing.T) { | |||
| 2087 | 2108 | defer mainWindow.Destroy() | |
| 2088 | 2109 | ||
| 2089 | 2110 | bw := &browserWindow{id: "window-1", mainWindow: mainWindow} | |
| 2111 | + | cfg := &config.Config{} | |
| 2112 | + | cfg.Workspace.HideTabBarWhenSingleTab = true | |
| 2090 | 2113 | app := &App{ | |
| 2091 | - | deps: &Dependencies{Config: &config.Config{}}, | |
| 2114 | + | deps: &Dependencies{}, | |
| 2115 | + | runtimeConfig: runtimeConfigSnapshotForTest(cfg), | |
| 2116 | + | runtimeConfigLoaded: true, | |
| 2092 | 2117 | } | |
| 2093 | - | app.deps.Config.Workspace.HideTabBarWhenSingleTab = true | |
| 2094 | 2118 | ||
| 2095 | 2119 | tab1 := entity.NewTab(entity.TabID("tab-1"), entity.WorkspaceID("workspace-1"), entity.NewPane(entity.PaneID("pane-1"))) | |
| 2096 | 2120 | tab2 := entity.NewTab(entity.TabID("tab-2"), entity.WorkspaceID("workspace-2"), entity.NewPane(entity.PaneID("pane-2"))) | |
| @@ -2142,13 +2166,16 @@ func TestApp_HandlePaneFullscreenChanged_ClearsBottomInset(t *testing.T) { | |||
| 2142 | 2166 | tabs.SetActive(tab2.ID) | |
| 2143 | 2167 | ||
| 2144 | 2168 | bw := &browserWindow{id: "window-1", mainWindow: mainWindow, tabs: tabs} | |
| 2169 | + | cfg := &config.Config{} | |
| 2170 | + | cfg.Workspace.HideTabBarWhenSingleTab = true | |
| 2145 | 2171 | app := &App{ | |
| 2146 | 2172 | tabs: tabs, | |
| 2147 | - | deps: &Dependencies{Config: &config.Config{}}, | |
| 2173 | + | deps: &Dependencies{}, | |
| 2174 | + | runtimeConfig: runtimeConfigSnapshotForTest(cfg), | |
| 2175 | + | runtimeConfigLoaded: true, | |
| 2148 | 2176 | browserWindows: map[string]*browserWindow{bw.id: bw}, | |
| 2149 | 2177 | lastFocusedWindowID: bw.id, | |
| 2150 | 2178 | } | |
| 2151 | - | app.deps.Config.Workspace.HideTabBarWhenSingleTab = true | |
| 2152 | 2179 | ||
| 2153 | 2180 | // Start with two tabs: inset should be present | |
| 2154 | 2181 | app.updateBrowserWindowTabBarVisibility(bw) | |
| @@ -2263,7 +2290,9 @@ func TestApp_CreatePopupTabUsesParentPaneOwnerWhenFocusIsStale(t *testing.T) { | |||
| 2263 | 2290 | first := &browserWindow{id: "window-1", tabs: firstTabs, mainWindow: &window.MainWindow{}} | |
| 2264 | 2291 | second := &browserWindow{id: "window-2", tabs: secondTabs, mainWindow: &window.MainWindow{}} | |
| 2265 | 2292 | app := &App{ | |
| 2266 | - | deps: &Dependencies{Config: &config.Config{}}, | |
| 2293 | + | deps: &Dependencies{}, | |
| 2294 | + | runtimeConfig: runtimeConfigSnapshotForTest(&config.Config{}), | |
| 2295 | + | runtimeConfigLoaded: true, | |
| 2267 | 2296 | tabs: globalTabs, | |
| 2268 | 2297 | tabsUC: usecase.NewManageTabsUseCase(func() string { return "popup-tab" }), | |
| 2269 | 2298 | browserWindows: map[string]*browserWindow{first.id: first, second.id: second}, | |
| @@ -2323,7 +2352,9 @@ func TestApp_TabCoordinatorCallbacksUseTargetWindowWhenFocusIsStale(t *testing.T | |||
| 2323 | 2352 | first := &browserWindow{id: "window-1", tabs: firstTabs, mainWindow: &window.MainWindow{}} | |
| 2324 | 2353 | second := &browserWindow{id: "window-2", tabs: secondTabs, mainWindow: &window.MainWindow{}} | |
| 2325 | 2354 | app := &App{ | |
| 2326 | - | deps: &Dependencies{Config: &config.Config{}}, | |
| 2355 | + | deps: &Dependencies{}, | |
| 2356 | + | runtimeConfig: runtimeConfigSnapshotForTest(&config.Config{}), | |
| 2357 | + | runtimeConfigLoaded: true, | |
| 2327 | 2358 | tabs: entity.NewTabList(), | |
| 2328 | 2359 | tabsUC: usecase.NewManageTabsUseCase(func() string { return "created-tab" }), | |
| 2329 | 2360 | browserWindows: map[string]*browserWindow{first.id: first, second.id: second}, | |
| @@ -2949,14 +2980,15 @@ func TestApp_DispatchBrowserWindowActionSwitchTabIndexUsesSourceWindow(t *testin | |||
| 2949 | 2980 | secondTabs.SetActive(secondTab1.ID) | |
| 2950 | 2981 | second := &browserWindow{id: "window-2", tabs: secondTabs} | |
| 2951 | 2982 | ||
| 2952 | - | app := &App{ | |
| 2953 | - | deps: &Dependencies{ | |
| 2954 | - | Config: &config.Config{ | |
| 2955 | - | Workspace: config.WorkspaceConfig{ | |
| 2956 | - | NewPaneURL: "about:blank", | |
| 2957 | - | }, | |
| 2958 | - | }, | |
| 2983 | + | cfg := &config.Config{ | |
| 2984 | + | Workspace: config.WorkspaceConfig{ | |
| 2985 | + | NewPaneURL: "about:blank", | |
| 2959 | 2986 | }, | |
| 2987 | + | } | |
| 2988 | + | app := &App{ | |
| 2989 | + | deps: &Dependencies{}, | |
| 2990 | + | runtimeConfig: runtimeConfigSnapshotForTest(cfg), | |
| 2991 | + | runtimeConfigLoaded: true, | |
| 2960 | 2992 | tabsUC: usecase.NewManageTabsUseCase(counterIDGen()), | |
| 2961 | 2993 | browserWindows: map[string]*browserWindow{first.id: first, second.id: second}, | |
| 2962 | 2994 | windowForTab: map[entity.TabID]*browserWindow{firstTab.ID: first, secondTab1.ID: second, secondTab2.ID: second}, | |
| @@ -2992,14 +3024,15 @@ func TestApp_DispatchBrowserWindowActionSwitchTabIndexCreatesOnlyOneMissingTab(t | |||
| 2992 | 3024 | secondTabs.SetActive(secondTab.ID) | |
| 2993 | 3025 | second := &browserWindow{id: "window-2", tabs: secondTabs} | |
| 2994 | 3026 | ||
| 2995 | - | app := &App{ | |
| 2996 | - | deps: &Dependencies{ | |
| 2997 | - | Config: &config.Config{ | |
| 2998 | - | Workspace: config.WorkspaceConfig{ | |
| 2999 | - | NewPaneURL: "about:blank", | |
| 3000 | - | }, | |
| 3001 | - | }, | |
| 3027 | + | cfg := &config.Config{ | |
| 3028 | + | Workspace: config.WorkspaceConfig{ | |
| 3029 | + | NewPaneURL: "about:blank", | |
| 3002 | 3030 | }, | |
| 3031 | + | } | |
| 3032 | + | app := &App{ | |
| 3033 | + | deps: &Dependencies{}, | |
| 3034 | + | runtimeConfig: runtimeConfigSnapshotForTest(cfg), | |
| 3035 | + | runtimeConfigLoaded: true, | |
| 3003 | 3036 | tabsUC: usecase.NewManageTabsUseCase(counterIDGen()), | |
| 3004 | 3037 | browserWindows: map[string]*browserWindow{first.id: first, second.id: second}, | |
| 3005 | 3038 | windowForTab: map[entity.TabID]*browserWindow{firstTab.ID: first, secondTab.ID: second}, | |
| @@ -3042,13 +3075,14 @@ func TestApp_SwitchBrowserWindowTabIndexNegativeIndexIsIgnored(t *testing.T) { | |||
| 3042 | 3075 | secondTabs.SetActive(secondTab.ID) | |
| 3043 | 3076 | second := &browserWindow{id: "window-2", tabs: secondTabs} | |
| 3044 | 3077 | ||
| 3078 | + | cfg := &config.Config{Workspace: config.WorkspaceConfig{NewPaneURL: "about:blank"}} | |
| 3045 | 3079 | app := &App{ | |
| 3046 | - | deps: &Dependencies{ | |
| 3047 | - | Config: &config.Config{Workspace: config.WorkspaceConfig{NewPaneURL: "about:blank"}}, | |
| 3048 | - | }, | |
| 3049 | - | tabsUC: usecase.NewManageTabsUseCase(counterIDGen()), | |
| 3050 | - | browserWindows: map[string]*browserWindow{second.id: second}, | |
| 3051 | - | windowForTab: map[entity.TabID]*browserWindow{secondTab.ID: second}, | |
| 3080 | + | deps: &Dependencies{}, | |
| 3081 | + | runtimeConfig: runtimeConfigSnapshotForTest(cfg), | |
| 3082 | + | runtimeConfigLoaded: true, | |
| 3083 | + | tabsUC: usecase.NewManageTabsUseCase(counterIDGen()), | |
| 3084 | + | browserWindows: map[string]*browserWindow{second.id: second}, | |
| 3085 | + | windowForTab: map[entity.TabID]*browserWindow{secondTab.ID: second}, | |
| 3052 | 3086 | } | |
| 3053 | 3087 | app.initTabCoordinator(ctx) | |
| 3054 | 3088 | ||
| @@ -3069,10 +3103,11 @@ func TestApp_DispatchBrowserWindowActionSwitchTabIndexNegativeDoesNotMutateWindo | |||
| 3069 | 3103 | ||
| 3070 | 3104 | second := &browserWindow{id: "window-2"} | |
| 3071 | 3105 | ||
| 3106 | + | cfg := &config.Config{Workspace: config.WorkspaceConfig{NewPaneURL: "about:blank"}} | |
| 3072 | 3107 | app := &App{ | |
| 3073 | - | deps: &Dependencies{ | |
| 3074 | - | Config: &config.Config{Workspace: config.WorkspaceConfig{NewPaneURL: "about:blank"}}, | |
| 3075 | - | }, | |
| 3108 | + | deps: &Dependencies{}, | |
| 3109 | + | runtimeConfig: runtimeConfigSnapshotForTest(cfg), | |
| 3110 | + | runtimeConfigLoaded: true, | |
| 3076 | 3111 | tabsUC: usecase.NewManageTabsUseCase(counterIDGen()), | |
| 3077 | 3112 | browserWindows: map[string]*browserWindow{second.id: second}, | |
| 3078 | 3113 | lastFocusedWindowID: "window-1", | |
| @@ -3101,10 +3136,12 @@ func TestApp_DispatchBrowserWindowActionSwitchTabIndexMissingURLDoesNotCreateTab | |||
| 3101 | 3136 | second := &browserWindow{id: "window-2", tabs: secondTabs} | |
| 3102 | 3137 | ||
| 3103 | 3138 | app := &App{ | |
| 3104 | - | deps: &Dependencies{Config: &config.Config{}}, | |
| 3105 | - | tabsUC: usecase.NewManageTabsUseCase(counterIDGen()), | |
| 3106 | - | browserWindows: map[string]*browserWindow{second.id: second}, | |
| 3107 | - | windowForTab: map[entity.TabID]*browserWindow{secondTab.ID: second}, | |
| 3139 | + | deps: &Dependencies{}, | |
| 3140 | + | runtimeConfig: runtimeConfigSnapshotForTest(&config.Config{}), | |
| 3141 | + | runtimeConfigLoaded: true, | |
| 3142 | + | tabsUC: usecase.NewManageTabsUseCase(counterIDGen()), | |
| 3143 | + | browserWindows: map[string]*browserWindow{second.id: second}, | |
| 3144 | + | windowForTab: map[entity.TabID]*browserWindow{secondTab.ID: second}, | |
| 3108 | 3145 | } | |
| 3109 | 3146 | app.initTabCoordinator(ctx) | |
| 3110 | 3147 | ||
| @@ -3126,7 +3163,9 @@ func TestApp_SwitchBrowserWindowTabIndexMissingURLDoesNotMutateWindowState(t *te | |||
| 3126 | 3163 | second := &browserWindow{id: "window-2"} | |
| 3127 | 3164 | ||
| 3128 | 3165 | app := &App{ | |
| 3129 | - | deps: &Dependencies{Config: &config.Config{}}, | |
| 3166 | + | deps: &Dependencies{}, | |
| 3167 | + | runtimeConfig: runtimeConfigSnapshotForTest(&config.Config{}), | |
| 3168 | + | runtimeConfigLoaded: true, | |
| 3130 | 3169 | tabsUC: usecase.NewManageTabsUseCase(counterIDGen()), | |
| 3131 | 3170 | browserWindows: map[string]*browserWindow{second.id: second}, | |
| 3132 | 3171 | lastFocusedWindowID: "window-1", | |
| @@ -3168,7 +3207,9 @@ func TestApp_WorkspaceOmniboxNavigateUsesOwnerWindow(t *testing.T) { | |||
| 3168 | 3207 | contentCoord.RegisterPopupWebView(entity.PaneID("pane-2"), recordingWv2) | |
| 3169 | 3208 | ||
| 3170 | 3209 | app := &App{ | |
| 3171 | - | deps: &Dependencies{Config: &config.Config{}}, | |
| 3210 | + | deps: &Dependencies{}, | |
| 3211 | + | runtimeConfig: runtimeConfigSnapshotForTest(&config.Config{}), | |
| 3212 | + | runtimeConfigLoaded: true, | |
| 3172 | 3213 | widgetFactory: layout.NewGtkWidgetFactory(), | |
| 3173 | 3214 | browserWindows: map[string]*browserWindow{first.id: first, second.id: second}, | |
| 3174 | 3215 | windowForTab: map[entity.TabID]*browserWindow{tab1.ID: first, tab2.ID: second}, | |
| @@ -3263,10 +3304,11 @@ func TestApp_RestoreSessionDoesNotLeakStaleWindowsIntoTabMerge(t *testing.T) { | |||
| 3263 | 3304 | ||
| 3264 | 3305 | app := &App{ | |
| 3265 | 3306 | deps: &Dependencies{ | |
| 3266 | - | Config: &config.Config{}, | |
| 3267 | 3307 | SessionStateRepo: mockSessionStateRepoWithSnapshot(t, sessionID, state), | |
| 3268 | 3308 | }, | |
| 3269 | - | mainWindow: mainWindow, | |
| 3309 | + | runtimeConfig: runtimeConfigSnapshotForTest(&config.Config{}), | |
| 3310 | + | runtimeConfigLoaded: true, | |
| 3311 | + | mainWindow: mainWindow, | |
| 3270 | 3312 | browserWindows: map[string]*browserWindow{ | |
| 3271 | 3313 | staleBW1.id: staleBW1, | |
| 3272 | 3314 | staleBW2.id: staleBW2, | |
| @@ -3343,15 +3385,16 @@ func TestApp_RestoreSessionHonorsActiveWindowIndex(t *testing.T) { | |||
| 3343 | 3385 | var runtimeW1 *browserWindow | |
| 3344 | 3386 | app := &App{ | |
| 3345 | 3387 | deps: &Dependencies{ | |
| 3346 | - | Config: &config.Config{}, | |
| 3347 | 3388 | SessionStateRepo: mockSessionStateRepoWithSnapshot(t, sessionID, state), | |
| 3348 | 3389 | }, | |
| 3349 | - | mainWindow: mainWindow, | |
| 3350 | - | widgetFactory: layout.NewGtkWidgetFactory(), | |
| 3351 | - | browserWindows: map[string]*browserWindow{firstBW.id: firstBW}, | |
| 3352 | - | tabs: entity.NewTabList(), | |
| 3353 | - | windowForTab: map[entity.TabID]*browserWindow{}, | |
| 3354 | - | workspaceViews: map[entity.TabID]*component.WorkspaceView{}, | |
| 3390 | + | runtimeConfig: runtimeConfigSnapshotForTest(&config.Config{}), | |
| 3391 | + | runtimeConfigLoaded: true, | |
| 3392 | + | mainWindow: mainWindow, | |
| 3393 | + | widgetFactory: layout.NewGtkWidgetFactory(), | |
| 3394 | + | browserWindows: map[string]*browserWindow{firstBW.id: firstBW}, | |
| 3395 | + | tabs: entity.NewTabList(), | |
| 3396 | + | windowForTab: map[entity.TabID]*browserWindow{}, | |
| 3397 | + | workspaceViews: map[entity.TabID]*component.WorkspaceView{}, | |
| 3355 | 3398 | browserWindowFactory: func(ctx context.Context, url string) (*browserWindow, error) { | |
| 3356 | 3399 | runtimeW1 = &browserWindow{id: "runtime-w1", mainWindow: mainWindow} | |
| 3357 | 3400 | return runtimeW1, nil | |
| @@ -3410,13 +3453,14 @@ func TestApp_RestoreSessionFailsOnAdditionalWindowCreationError(t *testing.T) { | |||
| 3410 | 3453 | factoryErr := errors.New("window creation failed") | |
| 3411 | 3454 | app := &App{ | |
| 3412 | 3455 | deps: &Dependencies{ | |
| 3413 | - | Config: &config.Config{}, | |
| 3414 | 3456 | SessionStateRepo: mockSessionStateRepoWithSnapshot(t, sessionID, state), | |
| 3415 | 3457 | }, | |
| 3416 | - | mainWindow: mainWindow, | |
| 3417 | - | browserWindows: map[string]*browserWindow{firstBW.id: firstBW}, | |
| 3418 | - | tabs: entity.NewTabList(), | |
| 3419 | - | windowForTab: map[entity.TabID]*browserWindow{}, | |
| 3458 | + | runtimeConfig: runtimeConfigSnapshotForTest(&config.Config{}), | |
| 3459 | + | runtimeConfigLoaded: true, | |
| 3460 | + | mainWindow: mainWindow, | |
| 3461 | + | browserWindows: map[string]*browserWindow{firstBW.id: firstBW}, | |
| 3462 | + | tabs: entity.NewTabList(), | |
| 3463 | + | windowForTab: map[entity.TabID]*browserWindow{}, | |
| 3420 | 3464 | browserWindowFactory: func(ctx context.Context, url string) (*browserWindow, error) { | |
| 3421 | 3465 | return nil, factoryErr | |
| 3422 | 3466 | }, | |
| @@ -1016,13 +1016,15 @@ func TestApp_CheckConfigMigrationUsesLastFocusedBrowserWindowToaster(t *testing. | |||||
| 1016 | migrator.EXPECT().CheckMigration().Return(&port.MigrationResult{MissingKeys: []string{"update.notify_on_new_settings"}}, nil).Once() | 1016 | migrator.EXPECT().CheckMigration().Return(&port.MigrationResult{MissingKeys: []string{"update.notify_on_new_settings"}}, nil).Once() | ||
| 1017 | 1017 | ||||
| 1018 | bw := &browserWindow{id: "window-1", appToaster: toaster} | 1018 | bw := &browserWindow{id: "window-1", appToaster: toaster} | ||
| 1019 | + | cfg := &config.Config{ | |||
| 1020 | + | Update: config.UpdateConfig{NotifyOnNewSettings: true}, | |||
| 1021 | + | } | |||
| 1019 | app := &App{ | 1022 | app := &App{ | ||
| 1020 | deps: &Dependencies{ | 1023 | deps: &Dependencies{ | ||
| 1021 | - | Config: &config.Config{ | 1024 | + | MigrationChecker: migrator, |
| 1022 | - | Update: config.UpdateConfig{NotifyOnNewSettings: true}, | |||
| 1023 | - | }, | |||
| 1024 | - | ConfigMigrator: migrator, | |||
| 1025 | }, | 1025 | }, | ||
| 1026 | + | runtimeConfig: runtimeConfigSnapshotForTest(cfg), | |||
| 1027 | + | runtimeConfigLoaded: true, | |||
| 1026 | browserWindows: map[string]*browserWindow{bw.id: bw}, | 1028 | browserWindows: map[string]*browserWindow{bw.id: bw}, | ||
| 1027 | lastFocusedWindowID: bw.id, | 1029 | lastFocusedWindowID: bw.id, | ||
| 1028 | } | 1030 | } | ||
| @@ -1132,9 +1134,10 @@ func TestApp_RestoreSessionHandlesEmptyWindowSnapshotAsSuccess(t *testing.T) { | |||||
| 1132 | 1134 | ||||
| 1133 | app := &App{ | 1135 | app := &App{ | ||
| 1134 | deps: &Dependencies{ | 1136 | deps: &Dependencies{ | ||
| 1135 | - | Config: &config.Config{}, | |||
| 1136 | SessionStateRepo: mockSessionStateRepoWithSnapshot(t, sessionID, state), | 1137 | SessionStateRepo: mockSessionStateRepoWithSnapshot(t, sessionID, state), | ||
| 1137 | }, | 1138 | }, | ||
| 1139 | + | runtimeConfig: runtimeConfigSnapshotForTest(&config.Config{}), | |||
| 1140 | + | runtimeConfigLoaded: true, | |||
| 1138 | tabs: entity.NewTabList(), | 1141 | tabs: entity.NewTabList(), | ||
| 1139 | browserWindows: map[string]*browserWindow{staleWindow.id: staleWindow}, | 1142 | browserWindows: map[string]*browserWindow{staleWindow.id: staleWindow}, | ||
| 1140 | lastFocusedWindowID: staleWindow.id, | 1143 | lastFocusedWindowID: staleWindow.id, | ||
| @@ -1171,11 +1174,12 @@ func TestApp_CreateInitialTabNoFallbackAfterEmptyWindowRestore(t *testing.T) { | |||||
| 1171 | bw := &browserWindow{id: "window-1", tabs: windowTabs} | 1174 | bw := &browserWindow{id: "window-1", tabs: windowTabs} | ||
| 1172 | app := &App{ | 1175 | app := &App{ | ||
| 1173 | deps: &Dependencies{ | 1176 | deps: &Dependencies{ | ||
| 1174 | - | Config: &config.Config{}, | |||
| 1175 | RestoreSessionID: string(sessionID), | 1177 | RestoreSessionID: string(sessionID), | ||
| 1176 | InitialURL: "https://fallback.example", | 1178 | InitialURL: "https://fallback.example", | ||
| 1177 | SessionStateRepo: mockSessionStateRepoWithSnapshot(t, sessionID, state), | 1179 | SessionStateRepo: mockSessionStateRepoWithSnapshot(t, sessionID, state), | ||
| 1178 | }, | 1180 | }, | ||
| 1181 | + | runtimeConfig: runtimeConfigSnapshotForTest(&config.Config{}), | |||
| 1182 | + | runtimeConfigLoaded: true, | |||
| 1179 | tabs: entity.NewTabList(), | 1183 | tabs: entity.NewTabList(), | ||
| 1180 | tabsUC: tabsUC, | 1184 | tabsUC: tabsUC, | ||
| 1181 | tabCoord: coordinator.NewTabCoordinator(ctx, coordinator.TabCoordinatorConfig{TabsUC: tabsUC, Tabs: entity.NewTabList()}), | 1185 | tabCoord: coordinator.NewTabCoordinator(ctx, coordinator.TabCoordinatorConfig{TabsUC: tabsUC, Tabs: entity.NewTabList()}), | ||
| @@ -1213,15 +1217,16 @@ func TestApp_RestoreSessionClearsStaleUIStateBeforeApplyingRestoredTabs(t *testi | |||||
| 1213 | 1217 | ||||
| 1214 | app := &App{ | 1218 | app := &App{ | ||
| 1215 | deps: &Dependencies{ | 1219 | deps: &Dependencies{ | ||
| 1216 | - | Config: &config.Config{}, | |||
| 1217 | SessionStateRepo: mockSessionStateRepoWithSnapshot(t, restoredSessionID, entity.SnapshotFromTabList(restoredSessionID, restoredTabs)), | 1220 | SessionStateRepo: mockSessionStateRepoWithSnapshot(t, restoredSessionID, entity.SnapshotFromTabList(restoredSessionID, restoredTabs)), | ||
| 1218 | }, | 1221 | }, | ||
| 1219 | - | mainWindow: mainWindow, | 1222 | + | runtimeConfig: runtimeConfigSnapshotForTest(&config.Config{}), |
| 1220 | - | widgetFactory: layout.NewGtkWidgetFactory(), | 1223 | + | runtimeConfigLoaded: true, |
| 1221 | - | tabs: entity.NewTabList(), | 1224 | + | mainWindow: mainWindow, |
| 1222 | - | browserWindows: map[string]*browserWindow{staleWindow.id: staleWindow}, | 1225 | + | widgetFactory: layout.NewGtkWidgetFactory(), |
| 1223 | - | workspaceViews: map[entity.TabID]*component.WorkspaceView{staleTab.ID: &component.WorkspaceView{}}, | 1226 | + | tabs: entity.NewTabList(), |
| 1224 | - | windowForTab: map[entity.TabID]*browserWindow{staleTab.ID: staleWindow}, | 1227 | + | browserWindows: map[string]*browserWindow{staleWindow.id: staleWindow}, |
| 1228 | + | workspaceViews: map[entity.TabID]*component.WorkspaceView{staleTab.ID: &component.WorkspaceView{}}, | |||
| 1229 | + | windowForTab: map[entity.TabID]*browserWindow{staleTab.ID: staleWindow}, | |||
| 1225 | floatingSessions: map[floatingSessionKey]*floatingWorkspaceSession{ | 1230 | floatingSessions: map[floatingSessionKey]*floatingWorkspaceSession{ | ||
| 1226 | staleSessionKey: {}, | 1231 | staleSessionKey: {}, | ||
| 1227 | }, | 1232 | }, | ||
| @@ -1280,17 +1285,18 @@ func TestApp_RestoreSessionWiresStackedPaneTitleBarCallbacks(t *testing.T) { | |||||
| 1280 | 1285 | ||||
| 1281 | app := &App{ | 1286 | app := &App{ | ||
| 1282 | deps: &Dependencies{ | 1287 | deps: &Dependencies{ | ||
| 1283 | - | Config: &config.Config{}, | |||
| 1284 | SessionStateRepo: mockSessionStateRepoWithSnapshot(t, stackedSessionID, entity.SnapshotFromTabList(stackedSessionID, stackedTabs)), | 1288 | SessionStateRepo: mockSessionStateRepoWithSnapshot(t, stackedSessionID, entity.SnapshotFromTabList(stackedSessionID, stackedTabs)), | ||
| 1285 | }, | 1289 | }, | ||
| 1286 | - | mainWindow: mainWindow, | 1290 | + | runtimeConfig: runtimeConfigSnapshotForTest(&config.Config{}), |
| 1287 | - | widgetFactory: layout.NewGtkWidgetFactory(), | 1291 | + | runtimeConfigLoaded: true, |
| 1288 | - | contentCoord: &contentcoord.Coordinator{}, | 1292 | + | mainWindow: mainWindow, |
| 1289 | - | wsCoord: coordinator.NewWorkspaceCoordinator(context.Background(), coordinator.WorkspaceCoordinatorConfig{ContentCoord: &contentcoord.Coordinator{}}), | 1293 | + | widgetFactory: layout.NewGtkWidgetFactory(), |
| 1290 | - | tabs: entity.NewTabList(), | 1294 | + | contentCoord: &contentcoord.Coordinator{}, |
| 1291 | - | browserWindows: map[string]*browserWindow{}, | 1295 | + | wsCoord: coordinator.NewWorkspaceCoordinator(context.Background(), coordinator.WorkspaceCoordinatorConfig{ContentCoord: &contentcoord.Coordinator{}}), |
| 1292 | - | workspaceViews: map[entity.TabID]*component.WorkspaceView{}, | 1296 | + | tabs: entity.NewTabList(), |
| 1293 | - | windowForTab: map[entity.TabID]*browserWindow{}, | 1297 | + | browserWindows: map[string]*browserWindow{}, |
| 1298 | + | workspaceViews: map[entity.TabID]*component.WorkspaceView{}, | |||
| 1299 | + | windowForTab: map[entity.TabID]*browserWindow{}, | |||
| 1294 | } | 1300 | } | ||
| 1295 | 1301 | ||||
| 1296 | if err := app.restoreSession(context.Background(), stackedSessionID); err != nil { | 1302 | if err := app.restoreSession(context.Background(), stackedSessionID); err != nil { | ||
| @@ -1767,7 +1773,9 @@ func TestApp_MoveActivePaneToTabFromBrowserWindowAnchorsOwningWindow(t *testing. | |||||
| 1767 | app := &App{ | 1773 | app := &App{ | ||
| 1768 | tabs: tabs, | 1774 | tabs: tabs, | ||
| 1769 | movePaneToTabUC: usecase.NewMovePaneToTabUseCase(func() string { return "new-tab" }), | 1775 | movePaneToTabUC: usecase.NewMovePaneToTabUseCase(func() string { return "new-tab" }), | ||
| 1770 | - | deps: &Dependencies{Config: &config.Config{}}, | 1776 | + | deps: &Dependencies{}, |
| 1777 | + | runtimeConfig: runtimeConfigSnapshotForTest(&config.Config{}), | |||
| 1778 | + | runtimeConfigLoaded: true, | |||
| 1771 | browserWindows: map[string]*browserWindow{first.id: first, second.id: second}, | 1779 | browserWindows: map[string]*browserWindow{first.id: first, second.id: second}, | ||
| 1772 | lastFocusedWindowID: second.id, | 1780 | lastFocusedWindowID: second.id, | ||
| 1773 | } | 1781 | } | ||
| @@ -1804,7 +1812,9 @@ func TestApp_MoveActivePaneToTabFromBrowserWindowActivatesTargetOwnerForCrossWin | |||||
| 1804 | app := &App{ | 1812 | app := &App{ | ||
| 1805 | tabs: tabs, | 1813 | tabs: tabs, | ||
| 1806 | movePaneToTabUC: usecase.NewMovePaneToTabUseCase(func() string { return "generated" }), | 1814 | movePaneToTabUC: usecase.NewMovePaneToTabUseCase(func() string { return "generated" }), | ||
| 1807 | - | deps: &Dependencies{Config: &config.Config{}}, | 1815 | + | deps: &Dependencies{}, |
| 1816 | + | runtimeConfig: runtimeConfigSnapshotForTest(&config.Config{}), | |||
| 1817 | + | runtimeConfigLoaded: true, | |||
| 1808 | browserWindows: map[string]*browserWindow{first.id: first, second.id: second}, | 1818 | browserWindows: map[string]*browserWindow{first.id: first, second.id: second}, | ||
| 1809 | windowForTab: map[entity.TabID]*browserWindow{tab1.ID: first, tab2.ID: second, tab3.ID: second}, | 1819 | windowForTab: map[entity.TabID]*browserWindow{tab1.ID: first, tab2.ID: second, tab3.ID: second}, | ||
| 1810 | lastFocusedWindowID: first.id, | 1820 | lastFocusedWindowID: first.id, | ||
| @@ -1859,7 +1869,9 @@ func TestApp_MoveActivePaneToTabFromBrowserWindowCrossWindowDerivedMirror(t *tes | |||||
| 1859 | app := &App{ | 1869 | app := &App{ | ||
| 1860 | tabs: tabs, | 1870 | tabs: tabs, | ||
| 1861 | movePaneToTabUC: usecase.NewMovePaneToTabUseCase(func() string { return "generated" }), | 1871 | movePaneToTabUC: usecase.NewMovePaneToTabUseCase(func() string { return "generated" }), | ||
| 1862 | - | deps: &Dependencies{Config: &config.Config{}}, | 1872 | + | deps: &Dependencies{}, |
| 1873 | + | runtimeConfig: runtimeConfigSnapshotForTest(&config.Config{}), | |||
| 1874 | + | runtimeConfigLoaded: true, | |||
| 1863 | browserWindows: map[string]*browserWindow{first.id: first, second.id: second}, | 1875 | browserWindows: map[string]*browserWindow{first.id: first, second.id: second}, | ||
| 1864 | windowForTab: map[entity.TabID]*browserWindow{tab1.ID: first, tab3.ID: second}, | 1876 | windowForTab: map[entity.TabID]*browserWindow{tab1.ID: first, tab3.ID: second}, | ||
| 1865 | lastFocusedWindowID: first.id, | 1877 | lastFocusedWindowID: first.id, | ||
| @@ -1909,10 +1921,12 @@ func TestApp_InitKeyboardHandlerDoesNotReattachExistingWindowInput(t *testing.T) | |||||
| 1909 | globalShortcutHandler: sentinelShortcutHandler, | 1921 | globalShortcutHandler: sentinelShortcutHandler, | ||
| 1910 | } | 1922 | } | ||
| 1911 | app := &App{ | 1923 | app := &App{ | ||
| 1912 | - | deps: &Dependencies{Config: &config.Config{}}, | 1924 | + | deps: &Dependencies{}, |
| 1913 | - | mainWindow: mainWindow, | 1925 | + | runtimeConfig: runtimeConfigSnapshotForTest(&config.Config{}), |
| 1914 | - | browserWindows: map[string]*browserWindow{bw.id: bw}, | 1926 | + | runtimeConfigLoaded: true, |
| 1915 | - | kbDispatcher: dispatcher.NewKeyboardDispatcher(context.Background(), nil, nil, nil, nil, dispatcher.KeyboardActions{}, func(context.Context) entity.PaneID { return "" }), | 1927 | + | mainWindow: mainWindow, |
| 1928 | + | browserWindows: map[string]*browserWindow{bw.id: bw}, | |||
| 1929 | + | kbDispatcher: dispatcher.NewKeyboardDispatcher(context.Background(), nil, nil, nil, nil, dispatcher.KeyboardActions{}, func(context.Context) entity.PaneID { return "" }), | |||
| 1916 | } | 1930 | } | ||
| 1917 | 1931 | ||||
| 1918 | app.initKeyboardHandler(context.Background()) | 1932 | app.initKeyboardHandler(context.Background()) | ||
| @@ -1961,10 +1975,13 @@ func TestApp_UpdateBrowserWindowTabBarVisibilityAutoHidesSingleTabButPreservesAl | |||||
| 1961 | } | 1975 | } | ||
| 1962 | setWindowTabBar(t, mainWindow, tabBar) | 1976 | setWindowTabBar(t, mainWindow, tabBar) | ||
| 1963 | bw := &browserWindow{id: "window-1", mainWindow: mainWindow} | 1977 | bw := &browserWindow{id: "window-1", mainWindow: mainWindow} | ||
| 1978 | + | cfg := &config.Config{} | |||
| 1979 | + | cfg.Workspace.HideTabBarWhenSingleTab = true | |||
| 1964 | app := &App{ | 1980 | app := &App{ | ||
| 1965 | - | deps: &Dependencies{Config: &config.Config{}}, | 1981 | + | deps: &Dependencies{}, |
| 1982 | + | runtimeConfig: runtimeConfigSnapshotForTest(cfg), | |||
| 1983 | + | runtimeConfigLoaded: true, | |||
| 1966 | } | 1984 | } | ||
| 1967 | - | app.deps.Config.Workspace.HideTabBarWhenSingleTab = true | |||
| 1968 | 1985 | ||||
| 1969 | tab1 := entity.NewTab(entity.TabID("tab-1"), entity.WorkspaceID("workspace-1"), entity.NewPane(entity.PaneID("pane-1"))) | 1986 | tab1 := entity.NewTab(entity.TabID("tab-1"), entity.WorkspaceID("workspace-1"), entity.NewPane(entity.PaneID("pane-1"))) | ||
| 1970 | tab2 := entity.NewTab(entity.TabID("tab-2"), entity.WorkspaceID("workspace-2"), entity.NewPane(entity.PaneID("pane-2"))) | 1987 | tab2 := entity.NewTab(entity.TabID("tab-2"), entity.WorkspaceID("workspace-2"), entity.NewPane(entity.PaneID("pane-2"))) | ||
| @@ -2031,10 +2048,13 @@ func TestApp_UpdateBrowserWindowTabBarVisibilityHonorsHideWhenSingleTabDisabled( | |||||
| 2031 | } | 2048 | } | ||
| 2032 | setWindowTabBar(t, mainWindow, tabBar) | 2049 | setWindowTabBar(t, mainWindow, tabBar) | ||
| 2033 | bw := &browserWindow{id: "window-1", mainWindow: mainWindow} | 2050 | bw := &browserWindow{id: "window-1", mainWindow: mainWindow} | ||
| 2051 | + | cfg := &config.Config{} | |||
| 2052 | + | cfg.Workspace.HideTabBarWhenSingleTab = false | |||
| 2034 | app := &App{ | 2053 | app := &App{ | ||
| 2035 | - | deps: &Dependencies{Config: &config.Config{}}, | 2054 | + | deps: &Dependencies{}, |
| 2055 | + | runtimeConfig: runtimeConfigSnapshotForTest(cfg), | |||
| 2056 | + | runtimeConfigLoaded: true, | |||
| 2036 | } | 2057 | } | ||
| 2037 | - | app.deps.Config.Workspace.HideTabBarWhenSingleTab = false | |||
| 2038 | 2058 | ||||
| 2039 | app.updateBrowserWindowTabBarVisibility(bw) | 2059 | app.updateBrowserWindowTabBarVisibility(bw) | ||
| 2040 | 2060 | ||||
| @@ -2064,8 +2084,9 @@ func TestApp_BuildRestoredWindowUIUpdatesEmptyWindowTabBarVisibility(t *testing. | |||||
| 2064 | 2084 | ||||
| 2065 | mw.SetTabBarContentInsetVisible(true) | 2085 | mw.SetTabBarContentInsetVisible(true) | ||
| 2066 | bw := &browserWindow{id: "window-1", mainWindow: mw, tabs: entity.NewTabList()} | 2086 | bw := &browserWindow{id: "window-1", mainWindow: mw, tabs: entity.NewTabList()} | ||
| 2067 | - | app := &App{deps: &Dependencies{Config: &config.Config{}}} | 2087 | + | cfg := &config.Config{} |
| 2068 | - | app.deps.Config.Workspace.HideTabBarWhenSingleTab = true | 2088 | + | cfg.Workspace.HideTabBarWhenSingleTab = true |
| 2089 | + | app := appWithRuntimeConfigForTest(cfg) | |||
| 2069 | 2090 | ||||
| 2070 | app.buildRestoredWindowUI(context.Background(), []*browserWindow{bw}) | 2091 | app.buildRestoredWindowUI(context.Background(), []*browserWindow{bw}) | ||
| 2071 | 2092 | ||||
| @@ -2087,10 +2108,13 @@ func TestApp_UpdateBrowserWindowTabBarVisibility_BottomInset(t *testing.T) { | |||||
| 2087 | defer mainWindow.Destroy() | 2108 | defer mainWindow.Destroy() | ||
| 2088 | 2109 | ||||
| 2089 | bw := &browserWindow{id: "window-1", mainWindow: mainWindow} | 2110 | bw := &browserWindow{id: "window-1", mainWindow: mainWindow} | ||
| 2111 | + | cfg := &config.Config{} | |||
| 2112 | + | cfg.Workspace.HideTabBarWhenSingleTab = true | |||
| 2090 | app := &App{ | 2113 | app := &App{ | ||
| 2091 | - | deps: &Dependencies{Config: &config.Config{}}, | 2114 | + | deps: &Dependencies{}, |
| 2115 | + | runtimeConfig: runtimeConfigSnapshotForTest(cfg), | |||
| 2116 | + | runtimeConfigLoaded: true, | |||
| 2092 | } | 2117 | } | ||
| 2093 | - | app.deps.Config.Workspace.HideTabBarWhenSingleTab = true | |||
| 2094 | 2118 | ||||
| 2095 | tab1 := entity.NewTab(entity.TabID("tab-1"), entity.WorkspaceID("workspace-1"), entity.NewPane(entity.PaneID("pane-1"))) | 2119 | tab1 := entity.NewTab(entity.TabID("tab-1"), entity.WorkspaceID("workspace-1"), entity.NewPane(entity.PaneID("pane-1"))) | ||
| 2096 | tab2 := entity.NewTab(entity.TabID("tab-2"), entity.WorkspaceID("workspace-2"), entity.NewPane(entity.PaneID("pane-2"))) | 2120 | tab2 := entity.NewTab(entity.TabID("tab-2"), entity.WorkspaceID("workspace-2"), entity.NewPane(entity.PaneID("pane-2"))) | ||
| @@ -2142,13 +2166,16 @@ func TestApp_HandlePaneFullscreenChanged_ClearsBottomInset(t *testing.T) { | |||||
| 2142 | tabs.SetActive(tab2.ID) | 2166 | tabs.SetActive(tab2.ID) | ||
| 2143 | 2167 | ||||
| 2144 | bw := &browserWindow{id: "window-1", mainWindow: mainWindow, tabs: tabs} | 2168 | bw := &browserWindow{id: "window-1", mainWindow: mainWindow, tabs: tabs} | ||
| 2169 | + | cfg := &config.Config{} | |||
| 2170 | + | cfg.Workspace.HideTabBarWhenSingleTab = true | |||
| 2145 | app := &App{ | 2171 | app := &App{ | ||
| 2146 | tabs: tabs, | 2172 | tabs: tabs, | ||
| 2147 | - | deps: &Dependencies{Config: &config.Config{}}, | 2173 | + | deps: &Dependencies{}, |
| 2174 | + | runtimeConfig: runtimeConfigSnapshotForTest(cfg), | |||
| 2175 | + | runtimeConfigLoaded: true, | |||
| 2148 | browserWindows: map[string]*browserWindow{bw.id: bw}, | 2176 | browserWindows: map[string]*browserWindow{bw.id: bw}, | ||
| 2149 | lastFocusedWindowID: bw.id, | 2177 | lastFocusedWindowID: bw.id, | ||
| 2150 | } | 2178 | } | ||
| 2151 | - | app.deps.Config.Workspace.HideTabBarWhenSingleTab = true | |||
| 2152 | 2179 | ||||
| 2153 | // Start with two tabs: inset should be present | 2180 | // Start with two tabs: inset should be present | ||
| 2154 | app.updateBrowserWindowTabBarVisibility(bw) | 2181 | app.updateBrowserWindowTabBarVisibility(bw) | ||
| @@ -2263,7 +2290,9 @@ func TestApp_CreatePopupTabUsesParentPaneOwnerWhenFocusIsStale(t *testing.T) { | |||||
| 2263 | first := &browserWindow{id: "window-1", tabs: firstTabs, mainWindow: &window.MainWindow{}} | 2290 | first := &browserWindow{id: "window-1", tabs: firstTabs, mainWindow: &window.MainWindow{}} | ||
| 2264 | second := &browserWindow{id: "window-2", tabs: secondTabs, mainWindow: &window.MainWindow{}} | 2291 | second := &browserWindow{id: "window-2", tabs: secondTabs, mainWindow: &window.MainWindow{}} | ||
| 2265 | app := &App{ | 2292 | app := &App{ | ||
| 2266 | - | deps: &Dependencies{Config: &config.Config{}}, | 2293 | + | deps: &Dependencies{}, |
| 2294 | + | runtimeConfig: runtimeConfigSnapshotForTest(&config.Config{}), | |||
| 2295 | + | runtimeConfigLoaded: true, | |||
| 2267 | tabs: globalTabs, | 2296 | tabs: globalTabs, | ||
| 2268 | tabsUC: usecase.NewManageTabsUseCase(func() string { return "popup-tab" }), | 2297 | tabsUC: usecase.NewManageTabsUseCase(func() string { return "popup-tab" }), | ||
| 2269 | browserWindows: map[string]*browserWindow{first.id: first, second.id: second}, | 2298 | browserWindows: map[string]*browserWindow{first.id: first, second.id: second}, | ||
| @@ -2323,7 +2352,9 @@ func TestApp_TabCoordinatorCallbacksUseTargetWindowWhenFocusIsStale(t *testing.T | |||||
| 2323 | first := &browserWindow{id: "window-1", tabs: firstTabs, mainWindow: &window.MainWindow{}} | 2352 | first := &browserWindow{id: "window-1", tabs: firstTabs, mainWindow: &window.MainWindow{}} | ||
| 2324 | second := &browserWindow{id: "window-2", tabs: secondTabs, mainWindow: &window.MainWindow{}} | 2353 | second := &browserWindow{id: "window-2", tabs: secondTabs, mainWindow: &window.MainWindow{}} | ||
| 2325 | app := &App{ | 2354 | app := &App{ | ||
| 2326 | - | deps: &Dependencies{Config: &config.Config{}}, | 2355 | + | deps: &Dependencies{}, |
| 2356 | + | runtimeConfig: runtimeConfigSnapshotForTest(&config.Config{}), | |||
| 2357 | + | runtimeConfigLoaded: true, | |||
| 2327 | tabs: entity.NewTabList(), | 2358 | tabs: entity.NewTabList(), | ||
| 2328 | tabsUC: usecase.NewManageTabsUseCase(func() string { return "created-tab" }), | 2359 | tabsUC: usecase.NewManageTabsUseCase(func() string { return "created-tab" }), | ||
| 2329 | browserWindows: map[string]*browserWindow{first.id: first, second.id: second}, | 2360 | browserWindows: map[string]*browserWindow{first.id: first, second.id: second}, | ||
| @@ -2949,14 +2980,15 @@ func TestApp_DispatchBrowserWindowActionSwitchTabIndexUsesSourceWindow(t *testin | |||||
| 2949 | secondTabs.SetActive(secondTab1.ID) | 2980 | secondTabs.SetActive(secondTab1.ID) | ||
| 2950 | second := &browserWindow{id: "window-2", tabs: secondTabs} | 2981 | second := &browserWindow{id: "window-2", tabs: secondTabs} | ||
| 2951 | 2982 | ||||
| 2952 | - | app := &App{ | 2983 | + | cfg := &config.Config{ |
| 2953 | - | deps: &Dependencies{ | 2984 | + | Workspace: config.WorkspaceConfig{ |
| 2954 | - | Config: &config.Config{ | 2985 | + | NewPaneURL: "about:blank", |
| 2955 | - | Workspace: config.WorkspaceConfig{ | |||
| 2956 | - | NewPaneURL: "about:blank", | |||
| 2957 | - | }, | |||
| 2958 | - | }, | |||
| 2959 | }, | 2986 | }, | ||
| 2987 | + | } | |||
| 2988 | + | app := &App{ | |||
| 2989 | + | deps: &Dependencies{}, | |||
| 2990 | + | runtimeConfig: runtimeConfigSnapshotForTest(cfg), | |||
| 2991 | + | runtimeConfigLoaded: true, | |||
| 2960 | tabsUC: usecase.NewManageTabsUseCase(counterIDGen()), | 2992 | tabsUC: usecase.NewManageTabsUseCase(counterIDGen()), | ||
| 2961 | browserWindows: map[string]*browserWindow{first.id: first, second.id: second}, | 2993 | browserWindows: map[string]*browserWindow{first.id: first, second.id: second}, | ||
| 2962 | windowForTab: map[entity.TabID]*browserWindow{firstTab.ID: first, secondTab1.ID: second, secondTab2.ID: second}, | 2994 | windowForTab: map[entity.TabID]*browserWindow{firstTab.ID: first, secondTab1.ID: second, secondTab2.ID: second}, | ||
| @@ -2992,14 +3024,15 @@ func TestApp_DispatchBrowserWindowActionSwitchTabIndexCreatesOnlyOneMissingTab(t | |||||
| 2992 | secondTabs.SetActive(secondTab.ID) | 3024 | secondTabs.SetActive(secondTab.ID) | ||
| 2993 | second := &browserWindow{id: "window-2", tabs: secondTabs} | 3025 | second := &browserWindow{id: "window-2", tabs: secondTabs} | ||
| 2994 | 3026 | ||||
| 2995 | - | app := &App{ | 3027 | + | cfg := &config.Config{ |
| 2996 | - | deps: &Dependencies{ | 3028 | + | Workspace: config.WorkspaceConfig{ |
| 2997 | - | Config: &config.Config{ | 3029 | + | NewPaneURL: "about:blank", |
| 2998 | - | Workspace: config.WorkspaceConfig{ | |||
| 2999 | - | NewPaneURL: "about:blank", | |||
| 3000 | - | }, | |||
| 3001 | - | }, | |||
| 3002 | }, | 3030 | }, | ||
| 3031 | + | } | |||
| 3032 | + | app := &App{ | |||
| 3033 | + | deps: &Dependencies{}, | |||
| 3034 | + | runtimeConfig: runtimeConfigSnapshotForTest(cfg), | |||
| 3035 | + | runtimeConfigLoaded: true, | |||
| 3003 | tabsUC: usecase.NewManageTabsUseCase(counterIDGen()), | 3036 | tabsUC: usecase.NewManageTabsUseCase(counterIDGen()), | ||
| 3004 | browserWindows: map[string]*browserWindow{first.id: first, second.id: second}, | 3037 | browserWindows: map[string]*browserWindow{first.id: first, second.id: second}, | ||
| 3005 | windowForTab: map[entity.TabID]*browserWindow{firstTab.ID: first, secondTab.ID: second}, | 3038 | windowForTab: map[entity.TabID]*browserWindow{firstTab.ID: first, secondTab.ID: second}, | ||
| @@ -3042,13 +3075,14 @@ func TestApp_SwitchBrowserWindowTabIndexNegativeIndexIsIgnored(t *testing.T) { | |||||
| 3042 | secondTabs.SetActive(secondTab.ID) | 3075 | secondTabs.SetActive(secondTab.ID) | ||
| 3043 | second := &browserWindow{id: "window-2", tabs: secondTabs} | 3076 | second := &browserWindow{id: "window-2", tabs: secondTabs} | ||
| 3044 | 3077 | ||||
| 3078 | + | cfg := &config.Config{Workspace: config.WorkspaceConfig{NewPaneURL: "about:blank"}} | |||
| 3045 | app := &App{ | 3079 | app := &App{ | ||
| 3046 | - | deps: &Dependencies{ | 3080 | + | deps: &Dependencies{}, |
| 3047 | - | Config: &config.Config{Workspace: config.WorkspaceConfig{NewPaneURL: "about:blank"}}, | 3081 | + | runtimeConfig: runtimeConfigSnapshotForTest(cfg), |
| 3048 | - | }, | 3082 | + | runtimeConfigLoaded: true, |
| 3049 | - | tabsUC: usecase.NewManageTabsUseCase(counterIDGen()), | 3083 | + | tabsUC: usecase.NewManageTabsUseCase(counterIDGen()), |
| 3050 | - | browserWindows: map[string]*browserWindow{second.id: second}, | 3084 | + | browserWindows: map[string]*browserWindow{second.id: second}, |
| 3051 | - | windowForTab: map[entity.TabID]*browserWindow{secondTab.ID: second}, | 3085 | + | windowForTab: map[entity.TabID]*browserWindow{secondTab.ID: second}, |
| 3052 | } | 3086 | } | ||
| 3053 | app.initTabCoordinator(ctx) | 3087 | app.initTabCoordinator(ctx) | ||
| 3054 | 3088 | ||||
| @@ -3069,10 +3103,11 @@ func TestApp_DispatchBrowserWindowActionSwitchTabIndexNegativeDoesNotMutateWindo | |||||
| 3069 | 3103 | ||||
| 3070 | second := &browserWindow{id: "window-2"} | 3104 | second := &browserWindow{id: "window-2"} | ||
| 3071 | 3105 | ||||
| 3106 | + | cfg := &config.Config{Workspace: config.WorkspaceConfig{NewPaneURL: "about:blank"}} | |||
| 3072 | app := &App{ | 3107 | app := &App{ | ||
| 3073 | - | deps: &Dependencies{ | 3108 | + | deps: &Dependencies{}, |
| 3074 | - | Config: &config.Config{Workspace: config.WorkspaceConfig{NewPaneURL: "about:blank"}}, | 3109 | + | runtimeConfig: runtimeConfigSnapshotForTest(cfg), |
| 3075 | - | }, | 3110 | + | runtimeConfigLoaded: true, |
| 3076 | tabsUC: usecase.NewManageTabsUseCase(counterIDGen()), | 3111 | tabsUC: usecase.NewManageTabsUseCase(counterIDGen()), | ||
| 3077 | browserWindows: map[string]*browserWindow{second.id: second}, | 3112 | browserWindows: map[string]*browserWindow{second.id: second}, | ||
| 3078 | lastFocusedWindowID: "window-1", | 3113 | lastFocusedWindowID: "window-1", | ||
| @@ -3101,10 +3136,12 @@ func TestApp_DispatchBrowserWindowActionSwitchTabIndexMissingURLDoesNotCreateTab | |||||
| 3101 | second := &browserWindow{id: "window-2", tabs: secondTabs} | 3136 | second := &browserWindow{id: "window-2", tabs: secondTabs} | ||
| 3102 | 3137 | ||||
| 3103 | app := &App{ | 3138 | app := &App{ | ||
| 3104 | - | deps: &Dependencies{Config: &config.Config{}}, | 3139 | + | deps: &Dependencies{}, |
| 3105 | - | tabsUC: usecase.NewManageTabsUseCase(counterIDGen()), | 3140 | + | runtimeConfig: runtimeConfigSnapshotForTest(&config.Config{}), |
| 3106 | - | browserWindows: map[string]*browserWindow{second.id: second}, | 3141 | + | runtimeConfigLoaded: true, |
| 3107 | - | windowForTab: map[entity.TabID]*browserWindow{secondTab.ID: second}, | 3142 | + | tabsUC: usecase.NewManageTabsUseCase(counterIDGen()), |
| 3143 | + | browserWindows: map[string]*browserWindow{second.id: second}, | |||
| 3144 | + | windowForTab: map[entity.TabID]*browserWindow{secondTab.ID: second}, | |||
| 3108 | } | 3145 | } | ||
| 3109 | app.initTabCoordinator(ctx) | 3146 | app.initTabCoordinator(ctx) | ||
| 3110 | 3147 | ||||
| @@ -3126,7 +3163,9 @@ func TestApp_SwitchBrowserWindowTabIndexMissingURLDoesNotMutateWindowState(t *te | |||||
| 3126 | second := &browserWindow{id: "window-2"} | 3163 | second := &browserWindow{id: "window-2"} | ||
| 3127 | 3164 | ||||
| 3128 | app := &App{ | 3165 | app := &App{ | ||
| 3129 | - | deps: &Dependencies{Config: &config.Config{}}, | 3166 | + | deps: &Dependencies{}, |
| 3167 | + | runtimeConfig: runtimeConfigSnapshotForTest(&config.Config{}), | |||
| 3168 | + | runtimeConfigLoaded: true, | |||
| 3130 | tabsUC: usecase.NewManageTabsUseCase(counterIDGen()), | 3169 | tabsUC: usecase.NewManageTabsUseCase(counterIDGen()), | ||
| 3131 | browserWindows: map[string]*browserWindow{second.id: second}, | 3170 | browserWindows: map[string]*browserWindow{second.id: second}, | ||
| 3132 | lastFocusedWindowID: "window-1", | 3171 | lastFocusedWindowID: "window-1", | ||
| @@ -3168,7 +3207,9 @@ func TestApp_WorkspaceOmniboxNavigateUsesOwnerWindow(t *testing.T) { | |||||
| 3168 | contentCoord.RegisterPopupWebView(entity.PaneID("pane-2"), recordingWv2) | 3207 | contentCoord.RegisterPopupWebView(entity.PaneID("pane-2"), recordingWv2) | ||
| 3169 | 3208 | ||||
| 3170 | app := &App{ | 3209 | app := &App{ | ||
| 3171 | - | deps: &Dependencies{Config: &config.Config{}}, | 3210 | + | deps: &Dependencies{}, |
| 3211 | + | runtimeConfig: runtimeConfigSnapshotForTest(&config.Config{}), | |||
| 3212 | + | runtimeConfigLoaded: true, | |||
| 3172 | widgetFactory: layout.NewGtkWidgetFactory(), | 3213 | widgetFactory: layout.NewGtkWidgetFactory(), | ||
| 3173 | browserWindows: map[string]*browserWindow{first.id: first, second.id: second}, | 3214 | browserWindows: map[string]*browserWindow{first.id: first, second.id: second}, | ||
| 3174 | windowForTab: map[entity.TabID]*browserWindow{tab1.ID: first, tab2.ID: second}, | 3215 | windowForTab: map[entity.TabID]*browserWindow{tab1.ID: first, tab2.ID: second}, | ||
| @@ -3263,10 +3304,11 @@ func TestApp_RestoreSessionDoesNotLeakStaleWindowsIntoTabMerge(t *testing.T) { | |||||
| 3263 | 3304 | ||||
| 3264 | app := &App{ | 3305 | app := &App{ | ||
| 3265 | deps: &Dependencies{ | 3306 | deps: &Dependencies{ | ||
| 3266 | - | Config: &config.Config{}, | |||
| 3267 | SessionStateRepo: mockSessionStateRepoWithSnapshot(t, sessionID, state), | 3307 | SessionStateRepo: mockSessionStateRepoWithSnapshot(t, sessionID, state), | ||
| 3268 | }, | 3308 | }, | ||
| 3269 | - | mainWindow: mainWindow, | 3309 | + | runtimeConfig: runtimeConfigSnapshotForTest(&config.Config{}), |
| 3310 | + | runtimeConfigLoaded: true, | |||
| 3311 | + | mainWindow: mainWindow, | |||
| 3270 | browserWindows: map[string]*browserWindow{ | 3312 | browserWindows: map[string]*browserWindow{ | ||
| 3271 | staleBW1.id: staleBW1, | 3313 | staleBW1.id: staleBW1, | ||
| 3272 | staleBW2.id: staleBW2, | 3314 | staleBW2.id: staleBW2, | ||
| @@ -3343,15 +3385,16 @@ func TestApp_RestoreSessionHonorsActiveWindowIndex(t *testing.T) { | |||||
| 3343 | var runtimeW1 *browserWindow | 3385 | var runtimeW1 *browserWindow | ||
| 3344 | app := &App{ | 3386 | app := &App{ | ||
| 3345 | deps: &Dependencies{ | 3387 | deps: &Dependencies{ | ||
| 3346 | - | Config: &config.Config{}, | |||
| 3347 | SessionStateRepo: mockSessionStateRepoWithSnapshot(t, sessionID, state), | 3388 | SessionStateRepo: mockSessionStateRepoWithSnapshot(t, sessionID, state), | ||
| 3348 | }, | 3389 | }, | ||
| 3349 | - | mainWindow: mainWindow, | 3390 | + | runtimeConfig: runtimeConfigSnapshotForTest(&config.Config{}), |
| 3350 | - | widgetFactory: layout.NewGtkWidgetFactory(), | 3391 | + | runtimeConfigLoaded: true, |
| 3351 | - | browserWindows: map[string]*browserWindow{firstBW.id: firstBW}, | 3392 | + | mainWindow: mainWindow, |
| 3352 | - | tabs: entity.NewTabList(), | 3393 | + | widgetFactory: layout.NewGtkWidgetFactory(), |
| 3353 | - | windowForTab: map[entity.TabID]*browserWindow{}, | 3394 | + | browserWindows: map[string]*browserWindow{firstBW.id: firstBW}, |
| 3354 | - | workspaceViews: map[entity.TabID]*component.WorkspaceView{}, | 3395 | + | tabs: entity.NewTabList(), |
| 3396 | + | windowForTab: map[entity.TabID]*browserWindow{}, | |||
| 3397 | + | workspaceViews: map[entity.TabID]*component.WorkspaceView{}, | |||
| 3355 | browserWindowFactory: func(ctx context.Context, url string) (*browserWindow, error) { | 3398 | browserWindowFactory: func(ctx context.Context, url string) (*browserWindow, error) { | ||
| 3356 | runtimeW1 = &browserWindow{id: "runtime-w1", mainWindow: mainWindow} | 3399 | runtimeW1 = &browserWindow{id: "runtime-w1", mainWindow: mainWindow} | ||
| 3357 | return runtimeW1, nil | 3400 | return runtimeW1, nil | ||
| @@ -3410,13 +3453,14 @@ func TestApp_RestoreSessionFailsOnAdditionalWindowCreationError(t *testing.T) { | |||||
| 3410 | factoryErr := errors.New("window creation failed") | 3453 | factoryErr := errors.New("window creation failed") | ||
| 3411 | app := &App{ | 3454 | app := &App{ | ||
| 3412 | deps: &Dependencies{ | 3455 | deps: &Dependencies{ | ||
| 3413 | - | Config: &config.Config{}, | |||
| 3414 | SessionStateRepo: mockSessionStateRepoWithSnapshot(t, sessionID, state), | 3456 | SessionStateRepo: mockSessionStateRepoWithSnapshot(t, sessionID, state), | ||
| 3415 | }, | 3457 | }, | ||
| 3416 | - | mainWindow: mainWindow, | 3458 | + | runtimeConfig: runtimeConfigSnapshotForTest(&config.Config{}), |
| 3417 | - | browserWindows: map[string]*browserWindow{firstBW.id: firstBW}, | 3459 | + | runtimeConfigLoaded: true, |
| 3418 | - | tabs: entity.NewTabList(), | 3460 | + | mainWindow: mainWindow, |
| 3419 | - | windowForTab: map[entity.TabID]*browserWindow{}, | 3461 | + | browserWindows: map[string]*browserWindow{firstBW.id: firstBW}, |
| 3462 | + | tabs: entity.NewTabList(), | |||
| 3463 | + | windowForTab: map[entity.TabID]*browserWindow{}, | |||
| 3420 | browserWindowFactory: func(ctx context.Context, url string) (*browserWindow, error) { | 3464 | browserWindowFactory: func(ctx context.Context, url string) (*browserWindow, error) { | ||
| 3421 | return nil, factoryErr | 3465 | return nil, factoryErr | ||
| 3422 | }, | 3466 | }, | ||
M internal/ui/app_clipboard_test.go +0 -3
| @@ -8,7 +8,6 @@ import ( | |||
| 8 | 8 | "github.com/bnema/dumber/internal/application/port" | |
| 9 | 9 | portmocks "github.com/bnema/dumber/internal/application/port/mocks" | |
| 10 | 10 | "github.com/bnema/dumber/internal/domain/entity" | |
| 11 | - | "github.com/bnema/dumber/internal/infrastructure/config" | |
| 12 | 11 | "github.com/stretchr/testify/mock" | |
| 13 | 12 | "github.com/stretchr/testify/require" | |
| 14 | 13 | ) | |
| @@ -31,7 +30,6 @@ func TestNew_DoesNotWireClipboardOrchestratorWhenClipboardMissing(t *testing.T) | |||
| 31 | 30 | ||
| 32 | 31 | deps := &Dependencies{ | |
| 33 | 32 | Ctx: ctx, | |
| 34 | - | Config: &config.Config{}, | |
| 35 | 33 | RuntimeConfig: runtimeConfig, | |
| 36 | 34 | Engine: engine, | |
| 37 | 35 | HandlerDeps: port.HandlerDeps{ | |
| @@ -66,7 +64,6 @@ func TestNew_WiresClipboardOrchestratorWhenClipboardPresent(t *testing.T) { | |||
| 66 | 64 | ||
| 67 | 65 | deps := &Dependencies{ | |
| 68 | 66 | Ctx: ctx, | |
| 69 | - | Config: &config.Config{}, | |
| 70 | 67 | RuntimeConfig: runtimeConfig, | |
| 71 | 68 | Engine: engine, | |
| 72 | 69 | HandlerDeps: port.HandlerDeps{ | |
| @@ -8,7 +8,6 @@ import ( | |||||
| 8 | "github.com/bnema/dumber/internal/application/port" | 8 | "github.com/bnema/dumber/internal/application/port" | ||
| 9 | portmocks "github.com/bnema/dumber/internal/application/port/mocks" | 9 | portmocks "github.com/bnema/dumber/internal/application/port/mocks" | ||
| 10 | "github.com/bnema/dumber/internal/domain/entity" | 10 | "github.com/bnema/dumber/internal/domain/entity" | ||
| 11 | - | "github.com/bnema/dumber/internal/infrastructure/config" | |||
| 12 | "github.com/stretchr/testify/mock" | 11 | "github.com/stretchr/testify/mock" | ||
| 13 | "github.com/stretchr/testify/require" | 12 | "github.com/stretchr/testify/require" | ||
| 14 | ) | 13 | ) | ||
| @@ -31,7 +30,6 @@ func TestNew_DoesNotWireClipboardOrchestratorWhenClipboardMissing(t *testing.T) | |||||
| 31 | 30 | ||||
| 32 | deps := &Dependencies{ | 31 | deps := &Dependencies{ | ||
| 33 | Ctx: ctx, | 32 | Ctx: ctx, | ||
| 34 | - | Config: &config.Config{}, | |||
| 35 | RuntimeConfig: runtimeConfig, | 33 | RuntimeConfig: runtimeConfig, | ||
| 36 | Engine: engine, | 34 | Engine: engine, | ||
| 37 | HandlerDeps: port.HandlerDeps{ | 35 | HandlerDeps: port.HandlerDeps{ | ||
| @@ -66,7 +64,6 @@ func TestNew_WiresClipboardOrchestratorWhenClipboardPresent(t *testing.T) { | |||||
| 66 | 64 | ||||
| 67 | deps := &Dependencies{ | 65 | deps := &Dependencies{ | ||
| 68 | Ctx: ctx, | 66 | Ctx: ctx, | ||
| 69 | - | Config: &config.Config{}, | |||
| 70 | RuntimeConfig: runtimeConfig, | 67 | RuntimeConfig: runtimeConfig, | ||
| 71 | Engine: engine, | 68 | Engine: engine, | ||
| 72 | HandlerDeps: port.HandlerDeps{ | 69 | HandlerDeps: port.HandlerDeps{ | ||
M internal/ui/app_omnibox_config_test.go +28 -7
| @@ -3,20 +3,41 @@ package ui | |||
| 3 | 3 | import ( | |
| 4 | 4 | "testing" | |
| 5 | 5 | ||
| 6 | - | configpkg "github.com/bnema/dumber/internal/infrastructure/config" | |
| 6 | + | "github.com/bnema/dumber/internal/application/port" | |
| 7 | + | "github.com/bnema/dumber/internal/domain/entity" | |
| 7 | 8 | ) | |
| 8 | 9 | ||
| 9 | - | func TestBuildOmniboxConfig_UsesConfiguredMostVisitedDays(t *testing.T) { | |
| 10 | - | deps := &Dependencies{ | |
| 11 | - | Config: &configpkg.Config{ | |
| 12 | - | Omnibox: configpkg.OmniboxConfig{ | |
| 13 | - | MostVisitedDays: 7, | |
| 10 | + | func TestBuildOmniboxConfigUsesRuntimeUIConfig(t *testing.T) { | |
| 11 | + | deps := &Dependencies{} | |
| 12 | + | runtimeCfg := port.RuntimeUIConfig{ | |
| 13 | + | DefaultUIScale: 1.35, | |
| 14 | + | DefaultSearchEngine: "https://search.example/?q=%s", | |
| 15 | + | SearchShortcuts: map[string]port.RuntimeSearchShortcut{ | |
| 16 | + | "gh": { | |
| 17 | + | URL: "https://github.com/search?q=%s", | |
| 18 | + | Description: "GitHub", | |
| 14 | 19 | }, | |
| 15 | 20 | }, | |
| 21 | + | Omnibox: port.RuntimeOmniboxConfig{ | |
| 22 | + | InitialBehavior: entity.OmniboxInitialBehaviorMostVisited, | |
| 23 | + | MostVisitedDays: 7, | |
| 24 | + | }, | |
| 16 | 25 | } | |
| 17 | 26 | ||
| 18 | - | got := buildOmniboxConfig(deps, nil, omniboxCallbacks{}) | |
| 27 | + | got := buildOmniboxConfig(deps, runtimeCfg, nil, omniboxCallbacks{}) | |
| 19 | 28 | if got.MostVisitedDays != 7 { | |
| 20 | 29 | t.Fatalf("MostVisitedDays = %d, want 7", got.MostVisitedDays) | |
| 21 | 30 | } | |
| 31 | + | if got.DefaultSearch != "https://search.example/?q=%s" { | |
| 32 | + | t.Fatalf("DefaultSearch = %q, want runtime default search", got.DefaultSearch) | |
| 33 | + | } | |
| 34 | + | if got.InitialBehavior != entity.OmniboxInitialBehaviorMostVisited { | |
| 35 | + | t.Fatalf("InitialBehavior = %q, want most_visited", got.InitialBehavior) | |
| 36 | + | } | |
| 37 | + | if got.UIScale != 1.35 { | |
| 38 | + | t.Fatalf("UIScale = %v, want 1.35", got.UIScale) | |
| 39 | + | } | |
| 40 | + | if got.ShortcutsUC == nil { | |
| 41 | + | t.Fatal("ShortcutsUC is nil") | |
| 42 | + | } | |
| 22 | 43 | } | |
| @@ -3,20 +3,41 @@ package ui | |||||
| 3 | import ( | 3 | import ( | ||
| 4 | "testing" | 4 | "testing" | ||
| 5 | 5 | ||||
| 6 | - | configpkg "github.com/bnema/dumber/internal/infrastructure/config" | 6 | + | "github.com/bnema/dumber/internal/application/port" |
| 7 | + | "github.com/bnema/dumber/internal/domain/entity" | |||
| 7 | ) | 8 | ) | ||
| 8 | 9 | ||||
| 9 | - | func TestBuildOmniboxConfig_UsesConfiguredMostVisitedDays(t *testing.T) { | 10 | + | func TestBuildOmniboxConfigUsesRuntimeUIConfig(t *testing.T) { |
| 10 | - | deps := &Dependencies{ | 11 | + | deps := &Dependencies{} |
| 11 | - | Config: &configpkg.Config{ | 12 | + | runtimeCfg := port.RuntimeUIConfig{ |
| 12 | - | Omnibox: configpkg.OmniboxConfig{ | 13 | + | DefaultUIScale: 1.35, |
| 13 | - | MostVisitedDays: 7, | 14 | + | DefaultSearchEngine: "https://search.example/?q=%s", |
| 15 | + | SearchShortcuts: map[string]port.RuntimeSearchShortcut{ | |||
| 16 | + | "gh": { | |||
| 17 | + | URL: "https://github.com/search?q=%s", | |||
| 18 | + | Description: "GitHub", | |||
| 14 | }, | 19 | }, | ||
| 15 | }, | 20 | }, | ||
| 21 | + | Omnibox: port.RuntimeOmniboxConfig{ | |||
| 22 | + | InitialBehavior: entity.OmniboxInitialBehaviorMostVisited, | |||
| 23 | + | MostVisitedDays: 7, | |||
| 24 | + | }, | |||
| 16 | } | 25 | } | ||
| 17 | 26 | ||||
| 18 | - | got := buildOmniboxConfig(deps, nil, omniboxCallbacks{}) | 27 | + | got := buildOmniboxConfig(deps, runtimeCfg, nil, omniboxCallbacks{}) |
| 19 | if got.MostVisitedDays != 7 { | 28 | if got.MostVisitedDays != 7 { | ||
| 20 | t.Fatalf("MostVisitedDays = %d, want 7", got.MostVisitedDays) | 29 | t.Fatalf("MostVisitedDays = %d, want 7", got.MostVisitedDays) | ||
| 21 | } | 30 | } | ||
| 31 | + | if got.DefaultSearch != "https://search.example/?q=%s" { | |||
| 32 | + | t.Fatalf("DefaultSearch = %q, want runtime default search", got.DefaultSearch) | |||
| 33 | + | } | |||
| 34 | + | if got.InitialBehavior != entity.OmniboxInitialBehaviorMostVisited { | |||
| 35 | + | t.Fatalf("InitialBehavior = %q, want most_visited", got.InitialBehavior) | |||
| 36 | + | } | |||
| 37 | + | if got.UIScale != 1.35 { | |||
| 38 | + | t.Fatalf("UIScale = %v, want 1.35", got.UIScale) | |||
| 39 | + | } | |||
| 40 | + | if got.ShortcutsUC == nil { | |||
| 41 | + | t.Fatal("ShortcutsUC is nil") | |||
| 42 | + | } | |||
| 22 | } | 43 | } | ||
M internal/ui/browser_window.go +6 -4
| @@ -195,10 +195,11 @@ func (bw *browserWindow) initAccentPicker(ctx context.Context, a *App) { | |||
| 195 | 195 | ||
| 196 | 196 | func (bw *browserWindow) initSessionManager(ctx context.Context, a *App) { | |
| 197 | 197 | log := logging.FromContext(ctx) | |
| 198 | - | if bw == nil || a == nil || a.deps == nil || a.deps.Config == nil || bw.mainWindow == nil { | |
| 198 | + | if bw == nil || a == nil || a.deps == nil || bw.mainWindow == nil { | |
| 199 | 199 | log.Debug().Msg("deps or main window not available, skipping session manager") | |
| 200 | 200 | return | |
| 201 | 201 | } | |
| 202 | + | runtimeCfg := a.runtimeConfigSnapshot().UI | |
| 202 | 203 | ||
| 203 | 204 | var listSessionsUC *usecase.ListSessionsUseCase | |
| 204 | 205 | var deleteSessionUC *usecase.DeleteSessionUseCase | |
| @@ -217,7 +218,7 @@ func (bw *browserWindow) initSessionManager(ctx context.Context, a *App) { | |||
| 217 | 218 | ListSessionsUC: listSessionsUC, | |
| 218 | 219 | DeleteSessionUC: deleteSessionUC, | |
| 219 | 220 | CurrentSession: a.deps.CurrentSessionID, | |
| 220 | - | UIScale: a.deps.Config.DefaultUIScale, | |
| 221 | + | UIScale: runtimeCfg.DefaultUIScale, | |
| 221 | 222 | OnClose: func() { | |
| 222 | 223 | log.Debug().Msg("session manager closed") | |
| 223 | 224 | }, | |
| @@ -249,13 +250,14 @@ func (bw *browserWindow) initSessionManager(ctx context.Context, a *App) { | |||
| 249 | 250 | ||
| 250 | 251 | func (bw *browserWindow) initTabPicker(ctx context.Context, a *App) { | |
| 251 | 252 | log := logging.FromContext(ctx) | |
| 252 | - | if bw == nil || a == nil || a.deps == nil || a.deps.Config == nil { | |
| 253 | + | if bw == nil || a == nil || a.deps == nil { | |
| 253 | 254 | log.Debug().Msg("deps/config not available, skipping tab picker") | |
| 254 | 255 | return | |
| 255 | 256 | } | |
| 257 | + | runtimeCfg := a.runtimeConfigSnapshot().UI | |
| 256 | 258 | ||
| 257 | 259 | bw.tabPicker = component.NewTabPicker(ctx, component.TabPickerConfig{ | |
| 258 | - | UIScale: a.deps.Config.DefaultUIScale, | |
| 260 | + | UIScale: runtimeCfg.DefaultUIScale, | |
| 259 | 261 | OnClose: func() { | |
| 260 | 262 | log.Debug().Msg("tab picker closed") | |
| 261 | 263 | }, | |
| @@ -195,10 +195,11 @@ func (bw *browserWindow) initAccentPicker(ctx context.Context, a *App) { | |||||
| 195 | 195 | ||||
| 196 | func (bw *browserWindow) initSessionManager(ctx context.Context, a *App) { | 196 | func (bw *browserWindow) initSessionManager(ctx context.Context, a *App) { | ||
| 197 | log := logging.FromContext(ctx) | 197 | log := logging.FromContext(ctx) | ||
| 198 | - | if bw == nil || a == nil || a.deps == nil || a.deps.Config == nil || bw.mainWindow == nil { | 198 | + | if bw == nil || a == nil || a.deps == nil || bw.mainWindow == nil { |
| 199 | log.Debug().Msg("deps or main window not available, skipping session manager") | 199 | log.Debug().Msg("deps or main window not available, skipping session manager") | ||
| 200 | return | 200 | return | ||
| 201 | } | 201 | } | ||
| 202 | + | runtimeCfg := a.runtimeConfigSnapshot().UI | |||
| 202 | 203 | ||||
| 203 | var listSessionsUC *usecase.ListSessionsUseCase | 204 | var listSessionsUC *usecase.ListSessionsUseCase | ||
| 204 | var deleteSessionUC *usecase.DeleteSessionUseCase | 205 | var deleteSessionUC *usecase.DeleteSessionUseCase | ||
| @@ -217,7 +218,7 @@ func (bw *browserWindow) initSessionManager(ctx context.Context, a *App) { | |||||
| 217 | ListSessionsUC: listSessionsUC, | 218 | ListSessionsUC: listSessionsUC, | ||
| 218 | DeleteSessionUC: deleteSessionUC, | 219 | DeleteSessionUC: deleteSessionUC, | ||
| 219 | CurrentSession: a.deps.CurrentSessionID, | 220 | CurrentSession: a.deps.CurrentSessionID, | ||
| 220 | - | UIScale: a.deps.Config.DefaultUIScale, | 221 | + | UIScale: runtimeCfg.DefaultUIScale, |
| 221 | OnClose: func() { | 222 | OnClose: func() { | ||
| 222 | log.Debug().Msg("session manager closed") | 223 | log.Debug().Msg("session manager closed") | ||
| 223 | }, | 224 | }, | ||
| @@ -249,13 +250,14 @@ func (bw *browserWindow) initSessionManager(ctx context.Context, a *App) { | |||||
| 249 | 250 | ||||
| 250 | func (bw *browserWindow) initTabPicker(ctx context.Context, a *App) { | 251 | func (bw *browserWindow) initTabPicker(ctx context.Context, a *App) { | ||
| 251 | log := logging.FromContext(ctx) | 252 | log := logging.FromContext(ctx) | ||
| 252 | - | if bw == nil || a == nil || a.deps == nil || a.deps.Config == nil { | 253 | + | if bw == nil || a == nil || a.deps == nil { |
| 253 | log.Debug().Msg("deps/config not available, skipping tab picker") | 254 | log.Debug().Msg("deps/config not available, skipping tab picker") | ||
| 254 | return | 255 | return | ||
| 255 | } | 256 | } | ||
| 257 | + | runtimeCfg := a.runtimeConfigSnapshot().UI | |||
| 256 | 258 | ||||
| 257 | bw.tabPicker = component.NewTabPicker(ctx, component.TabPickerConfig{ | 259 | bw.tabPicker = component.NewTabPicker(ctx, component.TabPickerConfig{ | ||
| 258 | - | UIScale: a.deps.Config.DefaultUIScale, | 260 | + | UIScale: runtimeCfg.DefaultUIScale, |
| 259 | OnClose: func() { | 261 | OnClose: func() { | ||
| 260 | log.Debug().Msg("tab picker closed") | 262 | log.Debug().Msg("tab picker closed") | ||
| 261 | }, | 263 | }, | ||
M internal/ui/browser_window_history_sidebar.go +2 -2
| @@ -127,10 +127,10 @@ func historySidebarWidthConfig(widthPx int) window.SidebarWidthConfig { | |||
| 127 | 127 | // applySidebarWidthConfig applies the config-backed sidebar width to the | |
| 128 | 128 | // main window's sidebar. | |
| 129 | 129 | func (bw *browserWindow) applySidebarWidthConfig(a *App) { | |
| 130 | - | if bw == nil || bw.mainWindow == nil || a == nil || a.deps == nil || a.deps.Config == nil { | |
| 130 | + | if bw == nil || bw.mainWindow == nil || a == nil { | |
| 131 | 131 | return | |
| 132 | 132 | } | |
| 133 | - | bw.mainWindow.SetSidebarWidth(historySidebarWidthConfig(a.deps.Config.SidebarWidth)) | |
| 133 | + | bw.mainWindow.SetSidebarWidth(historySidebarWidthConfig(a.runtimeConfigSnapshot().UI.SidebarWidth)) | |
| 134 | 134 | } | |
| 135 | 135 | ||
| 136 | 136 | // toggleHistorySidebarAction is the keyboard-action handler for toggling the | |
| @@ -127,10 +127,10 @@ func historySidebarWidthConfig(widthPx int) window.SidebarWidthConfig { | |||||
| 127 | // applySidebarWidthConfig applies the config-backed sidebar width to the | 127 | // applySidebarWidthConfig applies the config-backed sidebar width to the | ||
| 128 | // main window's sidebar. | 128 | // main window's sidebar. | ||
| 129 | func (bw *browserWindow) applySidebarWidthConfig(a *App) { | 129 | func (bw *browserWindow) applySidebarWidthConfig(a *App) { | ||
| 130 | - | if bw == nil || bw.mainWindow == nil || a == nil || a.deps == nil || a.deps.Config == nil { | 130 | + | if bw == nil || bw.mainWindow == nil || a == nil { |
| 131 | return | 131 | return | ||
| 132 | } | 132 | } | ||
| 133 | - | bw.mainWindow.SetSidebarWidth(historySidebarWidthConfig(a.deps.Config.SidebarWidth)) | 133 | + | bw.mainWindow.SetSidebarWidth(historySidebarWidthConfig(a.runtimeConfigSnapshot().UI.SidebarWidth)) |
| 134 | } | 134 | } | ||
| 135 | 135 | ||||
| 136 | // toggleHistorySidebarAction is the keyboard-action handler for toggling the | 136 | // toggleHistorySidebarAction is the keyboard-action handler for toggling the | ||
M internal/ui/browser_window_history_sidebar_test.go +1 -1
| @@ -877,7 +877,7 @@ func TestHistorySidebarWidthConfig_DefaultValue(t *testing.T) { | |||
| 877 | 877 | // that applySidebarWidthConfig handles nil mainWindow without panic. | |
| 878 | 878 | func TestBrowserWindow_ApplySidebarWidthConfig_NilMainWindowIsSafe(t *testing.T) { | |
| 879 | 879 | bw := &browserWindow{mainWindow: nil} | |
| 880 | - | app := &App{deps: &Dependencies{Config: &config.Config{SidebarWidth: 300}}} | |
| 880 | + | app := appWithRuntimeConfigForTest(&config.Config{SidebarWidth: 300}) | |
| 881 | 881 | require.NotPanics(t, func() { bw.applySidebarWidthConfig(app) }) | |
| 882 | 882 | } | |
| 883 | 883 | ||
| @@ -877,7 +877,7 @@ func TestHistorySidebarWidthConfig_DefaultValue(t *testing.T) { | |||||
| 877 | // that applySidebarWidthConfig handles nil mainWindow without panic. | 877 | // that applySidebarWidthConfig handles nil mainWindow without panic. | ||
| 878 | func TestBrowserWindow_ApplySidebarWidthConfig_NilMainWindowIsSafe(t *testing.T) { | 878 | func TestBrowserWindow_ApplySidebarWidthConfig_NilMainWindowIsSafe(t *testing.T) { | ||
| 879 | bw := &browserWindow{mainWindow: nil} | 879 | bw := &browserWindow{mainWindow: nil} | ||
| 880 | - | app := &App{deps: &Dependencies{Config: &config.Config{SidebarWidth: 300}}} | 880 | + | app := appWithRuntimeConfigForTest(&config.Config{SidebarWidth: 300}) |
| 881 | require.NotPanics(t, func() { bw.applySidebarWidthConfig(app) }) | 881 | require.NotPanics(t, func() { bw.applySidebarWidthConfig(app) }) | ||
| 882 | } | 882 | } | ||
| 883 | 883 | ||||
M internal/ui/browser_window_test.go +30 -24
| @@ -137,14 +137,16 @@ func TestTabCoordinator_CloseReleasesClosedTabWorkspaceWebViews(t *testing.T) { | |||
| 137 | 137 | ||
| 138 | 138 | bw := &browserWindow{id: "window-1", tabs: tabs} | |
| 139 | 139 | app := &App{ | |
| 140 | - | deps: &Dependencies{Config: config.DefaultConfig()}, | |
| 141 | - | tabsUC: usecase.NewManageTabsUseCase(counterIDGen()), | |
| 142 | - | contentCoord: contentCoord, | |
| 143 | - | browserWindows: map[string]*browserWindow{bw.id: bw}, | |
| 144 | - | tabs: entity.NewTabList(), | |
| 145 | - | workspaceViews: map[entity.TabID]*component.WorkspaceView{closedTab.ID: {}, survivingTab.ID: {}}, | |
| 146 | - | windowForTab: map[entity.TabID]*browserWindow{closedTab.ID: bw, survivingTab.ID: bw}, | |
| 147 | - | floatingSessions: map[floatingSessionKey]*floatingWorkspaceSession{}, | |
| 140 | + | deps: &Dependencies{}, | |
| 141 | + | runtimeConfig: runtimeConfigSnapshotForTest(config.DefaultConfig()), | |
| 142 | + | runtimeConfigLoaded: true, | |
| 143 | + | tabsUC: usecase.NewManageTabsUseCase(counterIDGen()), | |
| 144 | + | contentCoord: contentCoord, | |
| 145 | + | browserWindows: map[string]*browserWindow{bw.id: bw}, | |
| 146 | + | tabs: entity.NewTabList(), | |
| 147 | + | workspaceViews: map[entity.TabID]*component.WorkspaceView{closedTab.ID: {}, survivingTab.ID: {}}, | |
| 148 | + | windowForTab: map[entity.TabID]*browserWindow{closedTab.ID: bw, survivingTab.ID: bw}, | |
| 149 | + | floatingSessions: map[floatingSessionKey]*floatingWorkspaceSession{}, | |
| 148 | 150 | } | |
| 149 | 151 | app.tabs.Add(closedTab) | |
| 150 | 152 | app.tabs.Add(survivingTab) | |
| @@ -180,14 +182,16 @@ func TestTabCoordinator_SwitchDoesNotReleaseWorkspaceWebViews(t *testing.T) { | |||
| 180 | 182 | ||
| 181 | 183 | bw := &browserWindow{id: "window-1", tabs: tabs} | |
| 182 | 184 | app := &App{ | |
| 183 | - | deps: &Dependencies{Config: config.DefaultConfig()}, | |
| 184 | - | tabsUC: usecase.NewManageTabsUseCase(counterIDGen()), | |
| 185 | - | contentCoord: contentCoord, | |
| 186 | - | browserWindows: map[string]*browserWindow{bw.id: bw}, | |
| 187 | - | tabs: entity.NewTabList(), | |
| 188 | - | workspaceViews: map[entity.TabID]*component.WorkspaceView{firstTab.ID: {}, secondTab.ID: {}}, | |
| 189 | - | windowForTab: map[entity.TabID]*browserWindow{firstTab.ID: bw, secondTab.ID: bw}, | |
| 190 | - | floatingSessions: map[floatingSessionKey]*floatingWorkspaceSession{}, | |
| 185 | + | deps: &Dependencies{}, | |
| 186 | + | runtimeConfig: runtimeConfigSnapshotForTest(config.DefaultConfig()), | |
| 187 | + | runtimeConfigLoaded: true, | |
| 188 | + | tabsUC: usecase.NewManageTabsUseCase(counterIDGen()), | |
| 189 | + | contentCoord: contentCoord, | |
| 190 | + | browserWindows: map[string]*browserWindow{bw.id: bw}, | |
| 191 | + | tabs: entity.NewTabList(), | |
| 192 | + | workspaceViews: map[entity.TabID]*component.WorkspaceView{firstTab.ID: {}, secondTab.ID: {}}, | |
| 193 | + | windowForTab: map[entity.TabID]*browserWindow{firstTab.ID: bw, secondTab.ID: bw}, | |
| 194 | + | floatingSessions: map[floatingSessionKey]*floatingWorkspaceSession{}, | |
| 191 | 195 | } | |
| 192 | 196 | app.tabs.Add(firstTab) | |
| 193 | 197 | app.tabs.Add(secondTab) | |
| @@ -214,14 +218,16 @@ func TestTabCoordinator_CloseLastTabReleasesWorkspaceBeforeWindowRemoval(t *test | |||
| 214 | 218 | ||
| 215 | 219 | bw := &browserWindow{id: "window-1", tabs: tabs} | |
| 216 | 220 | app := &App{ | |
| 217 | - | deps: &Dependencies{Config: config.DefaultConfig()}, | |
| 218 | - | tabsUC: usecase.NewManageTabsUseCase(counterIDGen()), | |
| 219 | - | contentCoord: contentCoord, | |
| 220 | - | browserWindows: map[string]*browserWindow{bw.id: bw}, | |
| 221 | - | tabs: entity.NewTabList(), | |
| 222 | - | workspaceViews: map[entity.TabID]*component.WorkspaceView{closedTab.ID: {}}, | |
| 223 | - | windowForTab: map[entity.TabID]*browserWindow{closedTab.ID: bw}, | |
| 224 | - | floatingSessions: map[floatingSessionKey]*floatingWorkspaceSession{}, | |
| 221 | + | deps: &Dependencies{}, | |
| 222 | + | runtimeConfig: runtimeConfigSnapshotForTest(config.DefaultConfig()), | |
| 223 | + | runtimeConfigLoaded: true, | |
| 224 | + | tabsUC: usecase.NewManageTabsUseCase(counterIDGen()), | |
| 225 | + | contentCoord: contentCoord, | |
| 226 | + | browserWindows: map[string]*browserWindow{bw.id: bw}, | |
| 227 | + | tabs: entity.NewTabList(), | |
| 228 | + | workspaceViews: map[entity.TabID]*component.WorkspaceView{closedTab.ID: {}}, | |
| 229 | + | windowForTab: map[entity.TabID]*browserWindow{closedTab.ID: bw}, | |
| 230 | + | floatingSessions: map[floatingSessionKey]*floatingWorkspaceSession{}, | |
| 225 | 231 | } | |
| 226 | 232 | app.tabs.Add(closedTab) | |
| 227 | 233 | app.initTabCoordinator(ctx) | |
| @@ -137,14 +137,16 @@ func TestTabCoordinator_CloseReleasesClosedTabWorkspaceWebViews(t *testing.T) { | |||||
| 137 | 137 | ||||
| 138 | bw := &browserWindow{id: "window-1", tabs: tabs} | 138 | bw := &browserWindow{id: "window-1", tabs: tabs} | ||
| 139 | app := &App{ | 139 | app := &App{ | ||
| 140 | - | deps: &Dependencies{Config: config.DefaultConfig()}, | 140 | + | deps: &Dependencies{}, |
| 141 | - | tabsUC: usecase.NewManageTabsUseCase(counterIDGen()), | 141 | + | runtimeConfig: runtimeConfigSnapshotForTest(config.DefaultConfig()), |
| 142 | - | contentCoord: contentCoord, | 142 | + | runtimeConfigLoaded: true, |
| 143 | - | browserWindows: map[string]*browserWindow{bw.id: bw}, | 143 | + | tabsUC: usecase.NewManageTabsUseCase(counterIDGen()), |
| 144 | - | tabs: entity.NewTabList(), | 144 | + | contentCoord: contentCoord, |
| 145 | - | workspaceViews: map[entity.TabID]*component.WorkspaceView{closedTab.ID: {}, survivingTab.ID: {}}, | 145 | + | browserWindows: map[string]*browserWindow{bw.id: bw}, |
| 146 | - | windowForTab: map[entity.TabID]*browserWindow{closedTab.ID: bw, survivingTab.ID: bw}, | 146 | + | tabs: entity.NewTabList(), |
| 147 | - | floatingSessions: map[floatingSessionKey]*floatingWorkspaceSession{}, | 147 | + | workspaceViews: map[entity.TabID]*component.WorkspaceView{closedTab.ID: {}, survivingTab.ID: {}}, |
| 148 | + | windowForTab: map[entity.TabID]*browserWindow{closedTab.ID: bw, survivingTab.ID: bw}, | |||
| 149 | + | floatingSessions: map[floatingSessionKey]*floatingWorkspaceSession{}, | |||
| 148 | } | 150 | } | ||
| 149 | app.tabs.Add(closedTab) | 151 | app.tabs.Add(closedTab) | ||
| 150 | app.tabs.Add(survivingTab) | 152 | app.tabs.Add(survivingTab) | ||
| @@ -180,14 +182,16 @@ func TestTabCoordinator_SwitchDoesNotReleaseWorkspaceWebViews(t *testing.T) { | |||||
| 180 | 182 | ||||
| 181 | bw := &browserWindow{id: "window-1", tabs: tabs} | 183 | bw := &browserWindow{id: "window-1", tabs: tabs} | ||
| 182 | app := &App{ | 184 | app := &App{ | ||
| 183 | - | deps: &Dependencies{Config: config.DefaultConfig()}, | 185 | + | deps: &Dependencies{}, |
| 184 | - | tabsUC: usecase.NewManageTabsUseCase(counterIDGen()), | 186 | + | runtimeConfig: runtimeConfigSnapshotForTest(config.DefaultConfig()), |
| 185 | - | contentCoord: contentCoord, | 187 | + | runtimeConfigLoaded: true, |
| 186 | - | browserWindows: map[string]*browserWindow{bw.id: bw}, | 188 | + | tabsUC: usecase.NewManageTabsUseCase(counterIDGen()), |
| 187 | - | tabs: entity.NewTabList(), | 189 | + | contentCoord: contentCoord, |
| 188 | - | workspaceViews: map[entity.TabID]*component.WorkspaceView{firstTab.ID: {}, secondTab.ID: {}}, | 190 | + | browserWindows: map[string]*browserWindow{bw.id: bw}, |
| 189 | - | windowForTab: map[entity.TabID]*browserWindow{firstTab.ID: bw, secondTab.ID: bw}, | 191 | + | tabs: entity.NewTabList(), |
| 190 | - | floatingSessions: map[floatingSessionKey]*floatingWorkspaceSession{}, | 192 | + | workspaceViews: map[entity.TabID]*component.WorkspaceView{firstTab.ID: {}, secondTab.ID: {}}, |
| 193 | + | windowForTab: map[entity.TabID]*browserWindow{firstTab.ID: bw, secondTab.ID: bw}, | |||
| 194 | + | floatingSessions: map[floatingSessionKey]*floatingWorkspaceSession{}, | |||
| 191 | } | 195 | } | ||
| 192 | app.tabs.Add(firstTab) | 196 | app.tabs.Add(firstTab) | ||
| 193 | app.tabs.Add(secondTab) | 197 | app.tabs.Add(secondTab) | ||
| @@ -214,14 +218,16 @@ func TestTabCoordinator_CloseLastTabReleasesWorkspaceBeforeWindowRemoval(t *test | |||||
| 214 | 218 | ||||
| 215 | bw := &browserWindow{id: "window-1", tabs: tabs} | 219 | bw := &browserWindow{id: "window-1", tabs: tabs} | ||
| 216 | app := &App{ | 220 | app := &App{ | ||
| 217 | - | deps: &Dependencies{Config: config.DefaultConfig()}, | 221 | + | deps: &Dependencies{}, |
| 218 | - | tabsUC: usecase.NewManageTabsUseCase(counterIDGen()), | 222 | + | runtimeConfig: runtimeConfigSnapshotForTest(config.DefaultConfig()), |
| 219 | - | contentCoord: contentCoord, | 223 | + | runtimeConfigLoaded: true, |
| 220 | - | browserWindows: map[string]*browserWindow{bw.id: bw}, | 224 | + | tabsUC: usecase.NewManageTabsUseCase(counterIDGen()), |
| 221 | - | tabs: entity.NewTabList(), | 225 | + | contentCoord: contentCoord, |
| 222 | - | workspaceViews: map[entity.TabID]*component.WorkspaceView{closedTab.ID: {}}, | 226 | + | browserWindows: map[string]*browserWindow{bw.id: bw}, |
| 223 | - | windowForTab: map[entity.TabID]*browserWindow{closedTab.ID: bw}, | 227 | + | tabs: entity.NewTabList(), |
| 224 | - | floatingSessions: map[floatingSessionKey]*floatingWorkspaceSession{}, | 228 | + | workspaceViews: map[entity.TabID]*component.WorkspaceView{closedTab.ID: {}}, |
| 229 | + | windowForTab: map[entity.TabID]*browserWindow{closedTab.ID: bw}, | |||
| 230 | + | floatingSessions: map[floatingSessionKey]*floatingWorkspaceSession{}, | |||
| 225 | } | 231 | } | ||
| 226 | app.tabs.Add(closedTab) | 232 | app.tabs.Add(closedTab) | ||
| 227 | app.initTabCoordinator(ctx) | 233 | app.initTabCoordinator(ctx) | ||
M internal/ui/config_migration.go +3 -3
| @@ -12,11 +12,11 @@ import ( | |||
| 12 | 12 | func (a *App) checkConfigMigration(ctx context.Context) { | |
| 13 | 13 | log := logging.FromContext(ctx) | |
| 14 | 14 | ||
| 15 | - | if a.deps.ConfigMigrator == nil { | |
| 15 | + | if a.deps.MigrationChecker == nil { | |
| 16 | 16 | return | |
| 17 | 17 | } | |
| 18 | 18 | ||
| 19 | - | result, err := a.deps.ConfigMigrator.CheckMigration() | |
| 19 | + | result, err := a.deps.MigrationChecker.CheckMigration() | |
| 20 | 20 | if err != nil { | |
| 21 | 21 | log.Debug().Err(err).Msg("config migration check failed") | |
| 22 | 22 | return | |
| @@ -32,7 +32,7 @@ func (a *App) checkConfigMigration(ctx context.Context) { | |||
| 32 | 32 | Msg("config migration available") | |
| 33 | 33 | ||
| 34 | 34 | // Show toast notification if enabled | |
| 35 | - | if a.deps.Config != nil && a.deps.Config.Update.NotifyOnNewSettings { | |
| 35 | + | if a.runtimeConfigSnapshot().UI.Update.NotifyOnNewSettings { | |
| 36 | 36 | msg := fmt.Sprintf("Config has %d new settings. Run 'dumber config migrate'", len(result.MissingKeys)) | |
| 37 | 37 | a.showToastOnLastFocusedBrowserWindow(ctx, msg, component.ToastInfo) | |
| 38 | 38 | } | |
| @@ -12,11 +12,11 @@ import ( | |||||
| 12 | func (a *App) checkConfigMigration(ctx context.Context) { | 12 | func (a *App) checkConfigMigration(ctx context.Context) { | ||
| 13 | log := logging.FromContext(ctx) | 13 | log := logging.FromContext(ctx) | ||
| 14 | 14 | ||||
| 15 | - | if a.deps.ConfigMigrator == nil { | 15 | + | if a.deps.MigrationChecker == nil { |
| 16 | return | 16 | return | ||
| 17 | } | 17 | } | ||
| 18 | 18 | ||||
| 19 | - | result, err := a.deps.ConfigMigrator.CheckMigration() | 19 | + | result, err := a.deps.MigrationChecker.CheckMigration() |
| 20 | if err != nil { | 20 | if err != nil { | ||
| 21 | log.Debug().Err(err).Msg("config migration check failed") | 21 | log.Debug().Err(err).Msg("config migration check failed") | ||
| 22 | return | 22 | return | ||
| @@ -32,7 +32,7 @@ func (a *App) checkConfigMigration(ctx context.Context) { | |||||
| 32 | Msg("config migration available") | 32 | Msg("config migration available") | ||
| 33 | 33 | ||||
| 34 | // Show toast notification if enabled | 34 | // Show toast notification if enabled | ||
| 35 | - | if a.deps.Config != nil && a.deps.Config.Update.NotifyOnNewSettings { | 35 | + | if a.runtimeConfigSnapshot().UI.Update.NotifyOnNewSettings { |
| 36 | msg := fmt.Sprintf("Config has %d new settings. Run 'dumber config migrate'", len(result.MissingKeys)) | 36 | msg := fmt.Sprintf("Config has %d new settings. Run 'dumber config migrate'", len(result.MissingKeys)) | ||
| 37 | a.showToastOnLastFocusedBrowserWindow(ctx, msg, component.ToastInfo) | 37 | a.showToastOnLastFocusedBrowserWindow(ctx, msg, component.ToastInfo) | ||
| 38 | } | 38 | } | ||
M internal/ui/deps.go +1 -12
| @@ -8,7 +8,6 @@ import ( | |||
| 8 | 8 | "github.com/bnema/dumber/internal/application/usecase" | |
| 9 | 9 | "github.com/bnema/dumber/internal/domain/entity" | |
| 10 | 10 | "github.com/bnema/dumber/internal/domain/repository" | |
| 11 | - | "github.com/bnema/dumber/internal/infrastructure/config" | |
| 12 | 11 | "github.com/bnema/dumber/internal/ui/adapter" | |
| 13 | 12 | "github.com/bnema/dumber/internal/ui/theme" | |
| 14 | 13 | "github.com/bnema/puregotk/v4/gtk" | |
| @@ -19,9 +18,7 @@ import ( | |||
| 19 | 18 | type Dependencies struct { | |
| 20 | 19 | // Core context and configuration | |
| 21 | 20 | Ctx context.Context | |
| 22 | - | Config *config.Config // TODO: replace with port interface in a later milestone | |
| 23 | 21 | RuntimeConfig port.RuntimeConfigProvider | |
| 24 | - | EngineSettingsPayload func() port.EngineSettingsPayload | |
| 25 | 22 | InitialURL string // URL to open on startup (optional) | |
| 26 | 23 | RestoreSessionID string // Session ID to restore on startup (optional) | |
| 27 | 24 | StartupCrashReports []string | |
| @@ -91,18 +88,13 @@ type Dependencies struct { | |||
| 91 | 88 | SessionSpawner port.SessionSpawner | |
| 92 | 89 | // FileSystem provides file operations (e.g., for download deduplication). | |
| 93 | 90 | FileSystem port.FileSystem | |
| 94 | - | // OnConfigChange registers a callback for config hot-reload. | |
| 95 | - | // The config pointer in deps.Config is updated in-place before the callback fires. | |
| 96 | - | OnConfigChange func(callback func()) | |
| 97 | - | // WatchConfig starts watching the config file for changes. | |
| 98 | - | WatchConfig func() error | |
| 99 | 91 | ||
| 100 | 92 | // Update management | |
| 101 | 93 | CheckUpdateUC *usecase.CheckUpdateUseCase | |
| 102 | 94 | ApplyUpdateUC *usecase.ApplyUpdateUseCase | |
| 103 | 95 | ||
| 104 | 96 | // Config migration checker (optional; nil disables migration notifications) | |
| 105 | - | ConfigMigrator port.ConfigMigrator | |
| 97 | + | MigrationChecker port.ConfigMigrator | |
| 106 | 98 | ||
| 107 | 99 | // LaunchExternalURL opens a URI with the system's default handler (e.g. xdg-open). | |
| 108 | 100 | // Used for external URL schemes (vscode://, spotify://, etc.). | |
| @@ -121,9 +113,6 @@ func (d *Dependencies) Validate() error { | |||
| 121 | 113 | if d.Ctx == nil { | |
| 122 | 114 | return ErrMissingDependency("Ctx") | |
| 123 | 115 | } | |
| 124 | - | if d.Config == nil { | |
| 125 | - | return ErrMissingDependency("Config") | |
| 126 | - | } | |
| 127 | 116 | if d.RuntimeConfig == nil { | |
| 128 | 117 | return ErrMissingDependency("RuntimeConfig") | |
| 129 | 118 | } | |
| @@ -8,7 +8,6 @@ import ( | |||||
| 8 | "github.com/bnema/dumber/internal/application/usecase" | 8 | "github.com/bnema/dumber/internal/application/usecase" | ||
| 9 | "github.com/bnema/dumber/internal/domain/entity" | 9 | "github.com/bnema/dumber/internal/domain/entity" | ||
| 10 | "github.com/bnema/dumber/internal/domain/repository" | 10 | "github.com/bnema/dumber/internal/domain/repository" | ||
| 11 | - | "github.com/bnema/dumber/internal/infrastructure/config" | |||
| 12 | "github.com/bnema/dumber/internal/ui/adapter" | 11 | "github.com/bnema/dumber/internal/ui/adapter" | ||
| 13 | "github.com/bnema/dumber/internal/ui/theme" | 12 | "github.com/bnema/dumber/internal/ui/theme" | ||
| 14 | "github.com/bnema/puregotk/v4/gtk" | 13 | "github.com/bnema/puregotk/v4/gtk" | ||
| @@ -19,9 +18,7 @@ import ( | |||||
| 19 | type Dependencies struct { | 18 | type Dependencies struct { | ||
| 20 | // Core context and configuration | 19 | // Core context and configuration | ||
| 21 | Ctx context.Context | 20 | Ctx context.Context | ||
| 22 | - | Config *config.Config // TODO: replace with port interface in a later milestone | |||
| 23 | RuntimeConfig port.RuntimeConfigProvider | 21 | RuntimeConfig port.RuntimeConfigProvider | ||
| 24 | - | EngineSettingsPayload func() port.EngineSettingsPayload | |||
| 25 | InitialURL string // URL to open on startup (optional) | 22 | InitialURL string // URL to open on startup (optional) | ||
| 26 | RestoreSessionID string // Session ID to restore on startup (optional) | 23 | RestoreSessionID string // Session ID to restore on startup (optional) | ||
| 27 | StartupCrashReports []string | 24 | StartupCrashReports []string | ||
| @@ -91,18 +88,13 @@ type Dependencies struct { | |||||
| 91 | SessionSpawner port.SessionSpawner | 88 | SessionSpawner port.SessionSpawner | ||
| 92 | // FileSystem provides file operations (e.g., for download deduplication). | 89 | // FileSystem provides file operations (e.g., for download deduplication). | ||
| 93 | FileSystem port.FileSystem | 90 | FileSystem port.FileSystem | ||
| 94 | - | // OnConfigChange registers a callback for config hot-reload. | |||
| 95 | - | // The config pointer in deps.Config is updated in-place before the callback fires. | |||
| 96 | - | OnConfigChange func(callback func()) | |||
| 97 | - | // WatchConfig starts watching the config file for changes. | |||
| 98 | - | WatchConfig func() error | |||
| 99 | 91 | ||||
| 100 | // Update management | 92 | // Update management | ||
| 101 | CheckUpdateUC *usecase.CheckUpdateUseCase | 93 | CheckUpdateUC *usecase.CheckUpdateUseCase | ||
| 102 | ApplyUpdateUC *usecase.ApplyUpdateUseCase | 94 | ApplyUpdateUC *usecase.ApplyUpdateUseCase | ||
| 103 | 95 | ||||
| 104 | // Config migration checker (optional; nil disables migration notifications) | 96 | // Config migration checker (optional; nil disables migration notifications) | ||
| 105 | - | ConfigMigrator port.ConfigMigrator | 97 | + | MigrationChecker port.ConfigMigrator |
| 106 | 98 | ||||
| 107 | // LaunchExternalURL opens a URI with the system's default handler (e.g. xdg-open). | 99 | // LaunchExternalURL opens a URI with the system's default handler (e.g. xdg-open). | ||
| 108 | // Used for external URL schemes (vscode://, spotify://, etc.). | 100 | // Used for external URL schemes (vscode://, spotify://, etc.). | ||
| @@ -121,9 +113,6 @@ func (d *Dependencies) Validate() error { | |||||
| 121 | if d.Ctx == nil { | 113 | if d.Ctx == nil { | ||
| 122 | return ErrMissingDependency("Ctx") | 114 | return ErrMissingDependency("Ctx") | ||
| 123 | } | 115 | } | ||
| 124 | - | if d.Config == nil { | |||
| 125 | - | return ErrMissingDependency("Config") | |||
| 126 | - | } | |||
| 127 | if d.RuntimeConfig == nil { | 116 | if d.RuntimeConfig == nil { | ||
| 128 | return ErrMissingDependency("RuntimeConfig") | 117 | return ErrMissingDependency("RuntimeConfig") | ||
| 129 | } | 118 | } | ||
M internal/ui/external_theme_reload_test.go +14 -12
| @@ -11,6 +11,7 @@ import ( | |||
| 11 | 11 | "github.com/bnema/dumber/internal/application/port" | |
| 12 | 12 | portmocks "github.com/bnema/dumber/internal/application/port/mocks" | |
| 13 | 13 | "github.com/bnema/dumber/internal/application/usecase" | |
| 14 | + | "github.com/bnema/dumber/internal/bootstrap" | |
| 14 | 15 | "github.com/bnema/dumber/internal/domain/entity" | |
| 15 | 16 | "github.com/bnema/dumber/internal/infrastructure/config" | |
| 16 | 17 | "github.com/bnema/dumber/internal/shared/syncdispatch" | |
| @@ -103,19 +104,15 @@ func TestApplyAppearanceConfigSendsCompleteEngineSettingsPayload(t *testing.T) { | |||
| 103 | 104 | Return(nil). | |
| 104 | 105 | Once() | |
| 105 | 106 | app := &App{ | |
| 106 | - | deps: &Dependencies{ | |
| 107 | - | Config: cfg, | |
| 108 | - | EngineSettingsPayload: func() port.EngineSettingsPayload { | |
| 109 | - | return expected | |
| 110 | - | }, | |
| 111 | - | }, | |
| 112 | - | engine: engine, | |
| 107 | + | deps: &Dependencies{}, | |
| 108 | + | runtimeConfig: bootstrap.RuntimeConfigSnapshotFromConfig(cfg), | |
| 109 | + | runtimeConfigLoaded: true, | |
| 110 | + | engine: engine, | |
| 113 | 111 | } | |
| 114 | 112 | ||
| 115 | 113 | app.applyAppearanceConfig(ctx) | |
| 116 | 114 | ||
| 117 | 115 | require.Equal(t, expected, captured.Settings) | |
| 118 | - | require.Nil(t, captured.Raw) | |
| 119 | 116 | } | |
| 120 | 117 | ||
| 121 | 118 | func TestExternalThemeWatcherCallbackAppliesThemeThroughSharedPath(t *testing.T) { | |
| @@ -147,12 +144,13 @@ func TestExternalThemeWatcherCallbackAppliesThemeThroughSharedPath(t *testing.T) | |||
| 147 | 144 | engine.EXPECT().ContentInjector().Return(nil).Once() | |
| 148 | 145 | app := &App{ | |
| 149 | 146 | deps: &Dependencies{ | |
| 150 | - | Config: cfg, | |
| 151 | 147 | Theme: manager, | |
| 152 | 148 | ResolveThemeUC: usecase.NewResolveThemeUseCase(source), | |
| 153 | 149 | ExternalThemeSource: source, | |
| 154 | 150 | ExternalThemeWatcher: watcher, | |
| 155 | 151 | }, | |
| 152 | + | runtimeConfig: bootstrap.RuntimeConfigSnapshotFromConfig(cfg), | |
| 153 | + | runtimeConfigLoaded: true, | |
| 156 | 154 | dispatchOnMainThread: immediateDispatchForExternalThemeTest, | |
| 157 | 155 | engine: engine, | |
| 158 | 156 | } | |
| @@ -182,11 +180,12 @@ func TestExternalThemeReloadKeepsLastGoodAndDisablingClearsIt(t *testing.T) { | |||
| 182 | 180 | manager := uitheme.NewManager(ctx, resolvedThemeForUITest(cfg.Appearance.LightPalette, cfg.Appearance.DarkPalette)) | |
| 183 | 181 | app := &App{ | |
| 184 | 182 | deps: &Dependencies{ | |
| 185 | - | Config: cfg, | |
| 186 | 183 | Theme: manager, | |
| 187 | 184 | ResolveThemeUC: usecase.NewResolveThemeUseCase(source), | |
| 188 | 185 | ExternalThemeSource: source, | |
| 189 | 186 | }, | |
| 187 | + | runtimeConfig: bootstrap.RuntimeConfigSnapshotFromConfig(cfg), | |
| 188 | + | runtimeConfigLoaded: true, | |
| 190 | 189 | dispatchOnMainThread: immediateDispatchForExternalThemeTest, | |
| 191 | 190 | } | |
| 192 | 191 | ||
| @@ -203,6 +202,7 @@ func TestExternalThemeReloadKeepsLastGoodAndDisablingClearsIt(t *testing.T) { | |||
| 203 | 202 | require.Equal(t, "#000000", app.deps.Theme.GetCurrentPalette().Background) | |
| 204 | 203 | ||
| 205 | 204 | cfg.Appearance.ExternalTheme.Enabled = false | |
| 205 | + | app.updateRuntimeConfig(bootstrap.RuntimeConfigSnapshotFromConfig(cfg)) | |
| 206 | 206 | app.applyAppearanceConfig(ctx) | |
| 207 | 207 | require.Equal(t, cfg.Appearance.DarkPalette.Background, app.deps.Theme.GetCurrentPalette().Background) | |
| 208 | 208 | } | |
| @@ -221,11 +221,12 @@ func TestExternalThemeReloadKeepsLastGoodOnReadError(t *testing.T) { | |||
| 221 | 221 | manager := uitheme.NewManager(ctx, resolvedThemeForUITest(cfg.Appearance.LightPalette, cfg.Appearance.DarkPalette)) | |
| 222 | 222 | app := &App{ | |
| 223 | 223 | deps: &Dependencies{ | |
| 224 | - | Config: cfg, | |
| 225 | 224 | Theme: manager, | |
| 226 | 225 | ResolveThemeUC: usecase.NewResolveThemeUseCase(source), | |
| 227 | 226 | ExternalThemeSource: source, | |
| 228 | 227 | }, | |
| 228 | + | runtimeConfig: bootstrap.RuntimeConfigSnapshotFromConfig(cfg), | |
| 229 | + | runtimeConfigLoaded: true, | |
| 229 | 230 | dispatchOnMainThread: immediateDispatchForExternalThemeTest, | |
| 230 | 231 | } | |
| 231 | 232 | ||
| @@ -252,11 +253,12 @@ func TestExternalThemeReloadRecoversAfterMalformedUpdate(t *testing.T) { | |||
| 252 | 253 | manager := uitheme.NewManager(ctx, resolvedThemeForUITest(cfg.Appearance.LightPalette, cfg.Appearance.DarkPalette)) | |
| 253 | 254 | app := &App{ | |
| 254 | 255 | deps: &Dependencies{ | |
| 255 | - | Config: cfg, | |
| 256 | 256 | Theme: manager, | |
| 257 | 257 | ResolveThemeUC: usecase.NewResolveThemeUseCase(source), | |
| 258 | 258 | ExternalThemeSource: source, | |
| 259 | 259 | }, | |
| 260 | + | runtimeConfig: bootstrap.RuntimeConfigSnapshotFromConfig(cfg), | |
| 261 | + | runtimeConfigLoaded: true, | |
| 260 | 262 | dispatchOnMainThread: immediateDispatchForExternalThemeTest, | |
| 261 | 263 | } | |
| 262 | 264 | ||
| @@ -11,6 +11,7 @@ import ( | |||||
| 11 | "github.com/bnema/dumber/internal/application/port" | 11 | "github.com/bnema/dumber/internal/application/port" | ||
| 12 | portmocks "github.com/bnema/dumber/internal/application/port/mocks" | 12 | portmocks "github.com/bnema/dumber/internal/application/port/mocks" | ||
| 13 | "github.com/bnema/dumber/internal/application/usecase" | 13 | "github.com/bnema/dumber/internal/application/usecase" | ||
| 14 | + | "github.com/bnema/dumber/internal/bootstrap" | |||
| 14 | "github.com/bnema/dumber/internal/domain/entity" | 15 | "github.com/bnema/dumber/internal/domain/entity" | ||
| 15 | "github.com/bnema/dumber/internal/infrastructure/config" | 16 | "github.com/bnema/dumber/internal/infrastructure/config" | ||
| 16 | "github.com/bnema/dumber/internal/shared/syncdispatch" | 17 | "github.com/bnema/dumber/internal/shared/syncdispatch" | ||
| @@ -103,19 +104,15 @@ func TestApplyAppearanceConfigSendsCompleteEngineSettingsPayload(t *testing.T) { | |||||
| 103 | Return(nil). | 104 | Return(nil). | ||
| 104 | Once() | 105 | Once() | ||
| 105 | app := &App{ | 106 | app := &App{ | ||
| 106 | - | deps: &Dependencies{ | 107 | + | deps: &Dependencies{}, |
| 107 | - | Config: cfg, | 108 | + | runtimeConfig: bootstrap.RuntimeConfigSnapshotFromConfig(cfg), |
| 108 | - | EngineSettingsPayload: func() port.EngineSettingsPayload { | 109 | + | runtimeConfigLoaded: true, |
| 109 | - | return expected | 110 | + | engine: engine, |
| 110 | - | }, | |||
| 111 | - | }, | |||
| 112 | - | engine: engine, | |||
| 113 | } | 111 | } | ||
| 114 | 112 | ||||
| 115 | app.applyAppearanceConfig(ctx) | 113 | app.applyAppearanceConfig(ctx) | ||
| 116 | 114 | ||||
| 117 | require.Equal(t, expected, captured.Settings) | 115 | require.Equal(t, expected, captured.Settings) | ||
| 118 | - | require.Nil(t, captured.Raw) | |||
| 119 | } | 116 | } | ||
| 120 | 117 | ||||
| 121 | func TestExternalThemeWatcherCallbackAppliesThemeThroughSharedPath(t *testing.T) { | 118 | func TestExternalThemeWatcherCallbackAppliesThemeThroughSharedPath(t *testing.T) { | ||
| @@ -147,12 +144,13 @@ func TestExternalThemeWatcherCallbackAppliesThemeThroughSharedPath(t *testing.T) | |||||
| 147 | engine.EXPECT().ContentInjector().Return(nil).Once() | 144 | engine.EXPECT().ContentInjector().Return(nil).Once() | ||
| 148 | app := &App{ | 145 | app := &App{ | ||
| 149 | deps: &Dependencies{ | 146 | deps: &Dependencies{ | ||
| 150 | - | Config: cfg, | |||
| 151 | Theme: manager, | 147 | Theme: manager, | ||
| 152 | ResolveThemeUC: usecase.NewResolveThemeUseCase(source), | 148 | ResolveThemeUC: usecase.NewResolveThemeUseCase(source), | ||
| 153 | ExternalThemeSource: source, | 149 | ExternalThemeSource: source, | ||
| 154 | ExternalThemeWatcher: watcher, | 150 | ExternalThemeWatcher: watcher, | ||
| 155 | }, | 151 | }, | ||
| 152 | + | runtimeConfig: bootstrap.RuntimeConfigSnapshotFromConfig(cfg), | |||
| 153 | + | runtimeConfigLoaded: true, | |||
| 156 | dispatchOnMainThread: immediateDispatchForExternalThemeTest, | 154 | dispatchOnMainThread: immediateDispatchForExternalThemeTest, | ||
| 157 | engine: engine, | 155 | engine: engine, | ||
| 158 | } | 156 | } | ||
| @@ -182,11 +180,12 @@ func TestExternalThemeReloadKeepsLastGoodAndDisablingClearsIt(t *testing.T) { | |||||
| 182 | manager := uitheme.NewManager(ctx, resolvedThemeForUITest(cfg.Appearance.LightPalette, cfg.Appearance.DarkPalette)) | 180 | manager := uitheme.NewManager(ctx, resolvedThemeForUITest(cfg.Appearance.LightPalette, cfg.Appearance.DarkPalette)) | ||
| 183 | app := &App{ | 181 | app := &App{ | ||
| 184 | deps: &Dependencies{ | 182 | deps: &Dependencies{ | ||
| 185 | - | Config: cfg, | |||
| 186 | Theme: manager, | 183 | Theme: manager, | ||
| 187 | ResolveThemeUC: usecase.NewResolveThemeUseCase(source), | 184 | ResolveThemeUC: usecase.NewResolveThemeUseCase(source), | ||
| 188 | ExternalThemeSource: source, | 185 | ExternalThemeSource: source, | ||
| 189 | }, | 186 | }, | ||
| 187 | + | runtimeConfig: bootstrap.RuntimeConfigSnapshotFromConfig(cfg), | |||
| 188 | + | runtimeConfigLoaded: true, | |||
| 190 | dispatchOnMainThread: immediateDispatchForExternalThemeTest, | 189 | dispatchOnMainThread: immediateDispatchForExternalThemeTest, | ||
| 191 | } | 190 | } | ||
| 192 | 191 | ||||
| @@ -203,6 +202,7 @@ func TestExternalThemeReloadKeepsLastGoodAndDisablingClearsIt(t *testing.T) { | |||||
| 203 | require.Equal(t, "#000000", app.deps.Theme.GetCurrentPalette().Background) | 202 | require.Equal(t, "#000000", app.deps.Theme.GetCurrentPalette().Background) | ||
| 204 | 203 | ||||
| 205 | cfg.Appearance.ExternalTheme.Enabled = false | 204 | cfg.Appearance.ExternalTheme.Enabled = false | ||
| 205 | + | app.updateRuntimeConfig(bootstrap.RuntimeConfigSnapshotFromConfig(cfg)) | |||
| 206 | app.applyAppearanceConfig(ctx) | 206 | app.applyAppearanceConfig(ctx) | ||
| 207 | require.Equal(t, cfg.Appearance.DarkPalette.Background, app.deps.Theme.GetCurrentPalette().Background) | 207 | require.Equal(t, cfg.Appearance.DarkPalette.Background, app.deps.Theme.GetCurrentPalette().Background) | ||
| 208 | } | 208 | } | ||
| @@ -221,11 +221,12 @@ func TestExternalThemeReloadKeepsLastGoodOnReadError(t *testing.T) { | |||||
| 221 | manager := uitheme.NewManager(ctx, resolvedThemeForUITest(cfg.Appearance.LightPalette, cfg.Appearance.DarkPalette)) | 221 | manager := uitheme.NewManager(ctx, resolvedThemeForUITest(cfg.Appearance.LightPalette, cfg.Appearance.DarkPalette)) | ||
| 222 | app := &App{ | 222 | app := &App{ | ||
| 223 | deps: &Dependencies{ | 223 | deps: &Dependencies{ | ||
| 224 | - | Config: cfg, | |||
| 225 | Theme: manager, | 224 | Theme: manager, | ||
| 226 | ResolveThemeUC: usecase.NewResolveThemeUseCase(source), | 225 | ResolveThemeUC: usecase.NewResolveThemeUseCase(source), | ||
| 227 | ExternalThemeSource: source, | 226 | ExternalThemeSource: source, | ||
| 228 | }, | 227 | }, | ||
| 228 | + | runtimeConfig: bootstrap.RuntimeConfigSnapshotFromConfig(cfg), | |||
| 229 | + | runtimeConfigLoaded: true, | |||
| 229 | dispatchOnMainThread: immediateDispatchForExternalThemeTest, | 230 | dispatchOnMainThread: immediateDispatchForExternalThemeTest, | ||
| 230 | } | 231 | } | ||
| 231 | 232 | ||||
| @@ -252,11 +253,12 @@ func TestExternalThemeReloadRecoversAfterMalformedUpdate(t *testing.T) { | |||||
| 252 | manager := uitheme.NewManager(ctx, resolvedThemeForUITest(cfg.Appearance.LightPalette, cfg.Appearance.DarkPalette)) | 253 | manager := uitheme.NewManager(ctx, resolvedThemeForUITest(cfg.Appearance.LightPalette, cfg.Appearance.DarkPalette)) | ||
| 253 | app := &App{ | 254 | app := &App{ | ||
| 254 | deps: &Dependencies{ | 255 | deps: &Dependencies{ | ||
| 255 | - | Config: cfg, | |||
| 256 | Theme: manager, | 256 | Theme: manager, | ||
| 257 | ResolveThemeUC: usecase.NewResolveThemeUseCase(source), | 257 | ResolveThemeUC: usecase.NewResolveThemeUseCase(source), | ||
| 258 | ExternalThemeSource: source, | 258 | ExternalThemeSource: source, | ||
| 259 | }, | 259 | }, | ||
| 260 | + | runtimeConfig: bootstrap.RuntimeConfigSnapshotFromConfig(cfg), | |||
| 261 | + | runtimeConfigLoaded: true, | |||
| 260 | dispatchOnMainThread: immediateDispatchForExternalThemeTest, | 262 | dispatchOnMainThread: immediateDispatchForExternalThemeTest, | ||
| 261 | } | 263 | } | ||
| 262 | 264 | ||||
M internal/ui/runtime_config_test.go +24 -0
| @@ -1,13 +1,37 @@ | |||
| 1 | 1 | package ui | |
| 2 | 2 | ||
| 3 | 3 | import ( | |
| 4 | + | "context" | |
| 4 | 5 | "testing" | |
| 5 | 6 | ||
| 7 | + | "github.com/bnema/dumber/internal/application/dto" | |
| 6 | 8 | "github.com/bnema/dumber/internal/application/port" | |
| 7 | 9 | portmocks "github.com/bnema/dumber/internal/application/port/mocks" | |
| 8 | 10 | "github.com/bnema/dumber/internal/domain/entity" | |
| 9 | 11 | ) | |
| 10 | 12 | ||
| 13 | + | func TestDependenciesValidateAcceptsRuntimeConfigOnly(t *testing.T) { | |
| 14 | + | runtimeConfig := portmocks.NewMockRuntimeConfigProvider(t) | |
| 15 | + | engine := portmocks.NewMockEngine(t) | |
| 16 | + | deps := &Dependencies{ | |
| 17 | + | Ctx: context.Background(), | |
| 18 | + | RuntimeConfig: runtimeConfig, | |
| 19 | + | Engine: engine, | |
| 20 | + | HandlerDeps: port.HandlerDeps{ | |
| 21 | + | SaveConfig: func(context.Context, dto.WebUIConfig) error { | |
| 22 | + | return nil | |
| 23 | + | }, | |
| 24 | + | SaveOmniboxInitialBehavior: func(context.Context, entity.OmniboxInitialBehavior) error { | |
| 25 | + | return nil | |
| 26 | + | }, | |
| 27 | + | }, | |
| 28 | + | } | |
| 29 | + | ||
| 30 | + | if err := deps.Validate(); err != nil { | |
| 31 | + | t.Fatalf("Validate() error = %v, want nil", err) | |
| 32 | + | } | |
| 33 | + | } | |
| 34 | + | ||
| 11 | 35 | func TestAppRuntimeConfigReturnsProviderSnapshot(t *testing.T) { | |
| 12 | 36 | provider := portmocks.NewMockRuntimeConfigProvider(t) | |
| 13 | 37 | provider.EXPECT(). | |
| @@ -1,13 +1,37 @@ | |||||
| 1 | package ui | 1 | package ui | ||
| 2 | 2 | ||||
| 3 | import ( | 3 | import ( | ||
| 4 | + | "context" | |||
| 4 | "testing" | 5 | "testing" | ||
| 5 | 6 | ||||
| 7 | + | "github.com/bnema/dumber/internal/application/dto" | |||
| 6 | "github.com/bnema/dumber/internal/application/port" | 8 | "github.com/bnema/dumber/internal/application/port" | ||
| 7 | portmocks "github.com/bnema/dumber/internal/application/port/mocks" | 9 | portmocks "github.com/bnema/dumber/internal/application/port/mocks" | ||
| 8 | "github.com/bnema/dumber/internal/domain/entity" | 10 | "github.com/bnema/dumber/internal/domain/entity" | ||
| 9 | ) | 11 | ) | ||
| 10 | 12 | ||||
| 13 | + | func TestDependenciesValidateAcceptsRuntimeConfigOnly(t *testing.T) { | |||
| 14 | + | runtimeConfig := portmocks.NewMockRuntimeConfigProvider(t) | |||
| 15 | + | engine := portmocks.NewMockEngine(t) | |||
| 16 | + | deps := &Dependencies{ | |||
| 17 | + | Ctx: context.Background(), | |||
| 18 | + | RuntimeConfig: runtimeConfig, | |||
| 19 | + | Engine: engine, | |||
| 20 | + | HandlerDeps: port.HandlerDeps{ | |||
| 21 | + | SaveConfig: func(context.Context, dto.WebUIConfig) error { | |||
| 22 | + | return nil | |||
| 23 | + | }, | |||
| 24 | + | SaveOmniboxInitialBehavior: func(context.Context, entity.OmniboxInitialBehavior) error { | |||
| 25 | + | return nil | |||
| 26 | + | }, | |||
| 27 | + | }, | |||
| 28 | + | } | |||
| 29 | + | ||||
| 30 | + | if err := deps.Validate(); err != nil { | |||
| 31 | + | t.Fatalf("Validate() error = %v, want nil", err) | |||
| 32 | + | } | |||
| 33 | + | } | |||
| 34 | + | ||||
| 11 | func TestAppRuntimeConfigReturnsProviderSnapshot(t *testing.T) { | 35 | func TestAppRuntimeConfigReturnsProviderSnapshot(t *testing.T) { | ||
| 12 | provider := portmocks.NewMockRuntimeConfigProvider(t) | 36 | provider := portmocks.NewMockRuntimeConfigProvider(t) | ||
| 13 | provider.EXPECT(). | 37 | provider.EXPECT(). | ||
A internal/ui/runtime_config_test_helpers_test.go +19 -0
| @@ -0,0 +1,19 @@ | |||
| 1 | + | package ui | |
| 2 | + | ||
| 3 | + | import ( | |
| 4 | + | "github.com/bnema/dumber/internal/application/port" | |
| 5 | + | "github.com/bnema/dumber/internal/bootstrap" | |
| 6 | + | "github.com/bnema/dumber/internal/infrastructure/config" | |
| 7 | + | ) | |
| 8 | + | ||
| 9 | + | func runtimeConfigSnapshotForTest(cfg *config.Config) port.RuntimeConfigSnapshot { | |
| 10 | + | return bootstrap.RuntimeConfigSnapshotFromConfig(cfg) | |
| 11 | + | } | |
| 12 | + | ||
| 13 | + | func appWithRuntimeConfigForTest(cfg *config.Config) *App { | |
| 14 | + | return &App{ | |
| 15 | + | deps: &Dependencies{}, | |
| 16 | + | runtimeConfig: runtimeConfigSnapshotForTest(cfg), | |
| 17 | + | runtimeConfigLoaded: true, | |
| 18 | + | } | |
| 19 | + | } | |
| @@ -0,0 +1,19 @@ | |||||
| 1 | + | package ui | |||
| 2 | + | ||||
| 3 | + | import ( | |||
| 4 | + | "github.com/bnema/dumber/internal/application/port" | |||
| 5 | + | "github.com/bnema/dumber/internal/bootstrap" | |||
| 6 | + | "github.com/bnema/dumber/internal/infrastructure/config" | |||
| 7 | + | ) | |||
| 8 | + | ||||
| 9 | + | func runtimeConfigSnapshotForTest(cfg *config.Config) port.RuntimeConfigSnapshot { | |||
| 10 | + | return bootstrap.RuntimeConfigSnapshotFromConfig(cfg) | |||
| 11 | + | } | |||
| 12 | + | ||||
| 13 | + | func appWithRuntimeConfigForTest(cfg *config.Config) *App { | |||
| 14 | + | return &App{ | |||
| 15 | + | deps: &Dependencies{}, | |||
| 16 | + | runtimeConfig: runtimeConfigSnapshotForTest(cfg), | |||
| 17 | + | runtimeConfigLoaded: true, | |||
| 18 | + | } | |||
| 19 | + | } | |||