Rules-based browser launcher for TUI + GNOME. switchyard.aly.codes
tui gome bowser go
0

Configure Feed

Select the types of activity you want to include in your feed.

centralize default config

Aly Raffauf (Jul 1, 2026, 9:12 AM EDT) 5bb95987 a29bdd98

+102 -33
+2 -2
justfile
··· 57 57 # Run unit tests 58 58 test: 59 59 @echo "Running unit tests..." 60 - go test -v ./src/config_test.go ./src/url_test.go ./src/pattern_test.go ./src/validation_test.go ./src/redirection_test.go ./src/app.go ./src/config.go ./src/url.go ./src/pattern.go ./src/validation.go ./src/redirection.go 60 + go test -v ./src/config_test.go ./src/config_compat_test.go ./src/url_test.go ./src/pattern_test.go ./src/validation_test.go ./src/redirection_test.go ./src/app.go ./src/config.go ./src/url.go ./src/pattern.go ./src/validation.go ./src/redirection.go 61 61 62 62 # Run tests with coverage report 63 63 test-coverage: 64 64 @echo "Running tests with coverage..." 65 - go test -coverprofile=coverage.out ./src/config_test.go ./src/url_test.go ./src/pattern_test.go ./src/validation_test.go ./src/redirection_test.go ./src/app.go ./src/config.go ./src/url.go ./src/pattern.go ./src/validation.go ./src/redirection.go 65 + go test -coverprofile=coverage.out ./src/config_test.go ./src/config_compat_test.go ./src/url_test.go ./src/pattern_test.go ./src/validation_test.go ./src/redirection_test.go ./src/app.go ./src/config.go ./src/url.go ./src/pattern.go ./src/validation.go ./src/redirection.go 66 66 go tool cover -func=coverage.out 67 67 @echo "" 68 68 @echo "To view HTML coverage report, run: go tool cover -html=coverage.out"
+9 -8
src/config.go
··· 58 58 return filepath.Join(configDir(), "config.toml") 59 59 } 60 60 61 - func loadConfig() *Config { 62 - cfg := &Config{ 61 + func newDefaultConfig() *Config { 62 + return &Config{ 63 63 PromptOnClick: true, 64 64 CheckDefaultBrowser: true, 65 65 ShowAppNames: false, // Default: hide app names, show tooltips 66 66 ForceDarkMode: true, // Default: force dark mode 67 + StayAlive: true, 67 68 RemoveTrackingParameters: false, 68 69 Rules: []Rule{}, 69 70 } 71 + } 72 + 73 + func loadConfig() *Config { 74 + cfg := newDefaultConfig() 70 75 71 76 data, err := os.ReadFile(configPath()) 72 77 if err != nil { ··· 76 81 if err := toml.Unmarshal(data, cfg); err != nil { 77 82 fmt.Fprintf(os.Stderr, "Warning: Failed to parse config file: %v\n", err) 78 83 fmt.Fprintf(os.Stderr, "Using default configuration\n") 79 - return &Config{ 80 - PromptOnClick: true, 81 - CheckDefaultBrowser: true, 82 - Rules: []Rule{}, 83 - } 84 + return cfg 84 85 } 85 86 86 87 var compat struct { ··· 198 199 return err 199 200 } 200 201 201 - newCfg := &Config{} 202 + newCfg := newDefaultConfig() 202 203 if err := toml.Unmarshal(data, newCfg); err != nil { 203 204 return err 204 205 }
+89 -12
src/config_compat_test.go
··· 8 8 "testing" 9 9 ) 10 10 11 - func TestLoadConfigUsesLegacySanitizeLinksKey(t *testing.T) { 11 + func assertDefaultConfig(t *testing.T, cfg *Config) { 12 + t.Helper() 13 + 14 + if !cfg.PromptOnClick { 15 + t.Fatal("PromptOnClick default should be true") 16 + } 17 + if !cfg.CheckDefaultBrowser { 18 + t.Fatal("CheckDefaultBrowser default should be true") 19 + } 20 + if !cfg.ForceDarkMode { 21 + t.Fatal("ForceDarkMode default should be true") 22 + } 23 + if !cfg.StayAlive { 24 + t.Fatal("StayAlive default should be true") 25 + } 26 + if cfg.RemoveTrackingParameters { 27 + t.Fatal("RemoveTrackingParameters default should be false") 28 + } 29 + if cfg.Rules == nil { 30 + t.Fatal("Rules default should be an empty slice, not nil") 31 + } 32 + } 33 + 34 + func writeTestConfig(t *testing.T, configData string) string { 35 + t.Helper() 36 + 12 37 tempDir := t.TempDir() 13 38 t.Setenv("XDG_CONFIG_HOME", tempDir) 14 39 ··· 17 42 t.Fatalf("os.MkdirAll() error = %v", err) 18 43 } 19 44 20 - configData := []byte("sanitize_links = true\n") 21 - if err := os.WriteFile(filepath.Join(configDir, "config.toml"), configData, 0644); err != nil { 45 + configPath := filepath.Join(configDir, "config.toml") 46 + if err := os.WriteFile(configPath, []byte(configData), 0644); err != nil { 22 47 t.Fatalf("os.WriteFile() error = %v", err) 23 48 } 24 49 50 + return configPath 51 + } 52 + 53 + func TestLoadConfigUsesDefaultsWhenConfigIsMissing(t *testing.T) { 54 + t.Setenv("XDG_CONFIG_HOME", t.TempDir()) 55 + 25 56 cfg := loadConfig() 26 - if !cfg.RemoveTrackingParameters { 27 - t.Fatal("loadConfig() did not honor legacy sanitize_links key") 57 + 58 + assertDefaultConfig(t, cfg) 59 + } 60 + 61 + func TestLoadConfigUsesDefaultsWhenConfigIsInvalid(t *testing.T) { 62 + writeTestConfig(t, "prompt_on_click = [invalid\n") 63 + 64 + cfg := loadConfig() 65 + 66 + assertDefaultConfig(t, cfg) 67 + } 68 + 69 + func TestLoadConfigKeepsDefaultsForOmittedFields(t *testing.T) { 70 + writeTestConfig(t, "prompt_on_click = false\n") 71 + 72 + cfg := loadConfig() 73 + 74 + if cfg.PromptOnClick { 75 + t.Fatal("loadConfig() should honor explicit prompt_on_click = false") 76 + } 77 + if !cfg.ForceDarkMode { 78 + t.Fatal("loadConfig() should keep ForceDarkMode default when omitted") 79 + } 80 + if cfg.Rules == nil { 81 + t.Fatal("Rules default should be an empty slice, not nil") 28 82 } 29 83 } 30 84 31 - func TestLoadConfigPrefersNewTrackingParameterKey(t *testing.T) { 85 + func TestImportConfigKeepsDefaultsForOmittedFields(t *testing.T) { 32 86 tempDir := t.TempDir() 33 87 t.Setenv("XDG_CONFIG_HOME", tempDir) 34 88 35 - configDir := filepath.Join(tempDir, "switchyard") 36 - if err := os.MkdirAll(configDir, 0755); err != nil { 37 - t.Fatalf("os.MkdirAll() error = %v", err) 89 + importPath := filepath.Join(tempDir, "import.toml") 90 + if err := os.WriteFile(importPath, []byte("prompt_on_click = false\n"), 0644); err != nil { 91 + t.Fatalf("os.WriteFile() error = %v", err) 38 92 } 39 93 40 - configData := []byte("remove_tracking_parameters = false\nsanitize_links = true\n") 41 - if err := os.WriteFile(filepath.Join(configDir, "config.toml"), configData, 0644); err != nil { 42 - t.Fatalf("os.WriteFile() error = %v", err) 94 + cfg := &Config{} 95 + if err := importConfig(cfg, importPath); err != nil { 96 + t.Fatalf("importConfig() error = %v", err) 43 97 } 98 + 99 + if cfg.PromptOnClick { 100 + t.Fatal("importConfig() should honor explicit prompt_on_click = false") 101 + } 102 + if !cfg.ForceDarkMode { 103 + t.Fatal("importConfig() should keep ForceDarkMode default when omitted") 104 + } 105 + if cfg.Rules == nil { 106 + t.Fatal("Rules default should be an empty slice, not nil") 107 + } 108 + } 109 + 110 + func TestLoadConfigUsesLegacySanitizeLinksKey(t *testing.T) { 111 + writeTestConfig(t, "sanitize_links = true\n") 112 + 113 + cfg := loadConfig() 114 + if !cfg.RemoveTrackingParameters { 115 + t.Fatal("loadConfig() did not honor legacy sanitize_links key") 116 + } 117 + } 118 + 119 + func TestLoadConfigPrefersNewTrackingParameterKey(t *testing.T) { 120 + writeTestConfig(t, "remove_tracking_parameters = false\nsanitize_links = true\n") 44 121 45 122 cfg := loadConfig() 46 123 if cfg.RemoveTrackingParameters {
+1 -10
src/window_settings.go
··· 209 209 } 210 210 211 211 newCfg := loadConfig() 212 - cfg.PromptOnClick = newCfg.PromptOnClick 213 - cfg.FavoriteBrowser = newCfg.FavoriteBrowser 214 - cfg.CheckDefaultBrowser = newCfg.CheckDefaultBrowser 215 - cfg.ShowAppNames = newCfg.ShowAppNames 216 - cfg.ForceDarkMode = newCfg.ForceDarkMode 217 - cfg.StayAlive = newCfg.StayAlive 218 - cfg.RemoveTrackingParameters = newCfg.RemoveTrackingParameters 219 - cfg.HiddenBrowsers = newCfg.HiddenBrowsers 220 - cfg.Redirections = newCfg.Redirections 221 - cfg.Rules = newCfg.Rules 212 + *cfg = *newCfg 222 213 223 214 if onChange != nil { 224 215 onChange()
+1 -1
website/src/pages/index.astro
··· 70 70 icon: "lucide:zap", 71 71 title: "Fast & Native", 72 72 description: 73 - "A native GNOME app that launches instantly. No background services.", 73 + "A native GNOME app that stays ready and launches instantly.", 74 74 }, 75 75 ] as const; 76 76