From 276539c3831e16c65eb45aacaf0b5ee219688bfe Mon Sep 17 00:00:00 2001 From: Mikhail Yenuchenko Date: Tue, 4 Aug 2026 19:18:48 +0300 Subject: [PATCH] Make the branded GoSentry theme the default. Co-authored-by: Cursor --- README.md | 2 +- docs/CHANGELOG.md | 4 ++++ docs/TESTS.md | 2 +- src/app/operations.go | 4 ++-- src/domain/config.go | 6 +++--- src/storage/store.go | 2 +- src/storage/store_test.go | 4 ++-- src/ui/settings_view.go | 6 +++--- src/ui/theme.go | 12 ++++++------ src/ui/theme_test.go | 28 ++++++++++++++-------------- 10 files changed, 37 insertions(+), 33 deletions(-) diff --git a/README.md b/README.md index 4c90c94..c0f60a6 100644 --- a/README.md +++ b/README.md @@ -74,7 +74,7 @@ portable application: moving the program folder also moves its configuration. "execution_mode": "parallel", "overlap_policy": "skip", "default_timeout_seconds": 0, - "theme": "default", + "theme": "gosentry", "job_list_view": "detailed" } ``` diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 822e035..7a1b311 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -38,6 +38,10 @@ dragged.** **Settings:** +- **The branded GoSentry theme is now the default.** Fresh installs, the + **Defaults** button, and configs that omit `theme` all open in the teal/amber + look; users who prefer Fyne's built-in theme can still pick **Default** in + Settings. - The **Save / Cancel / Restore defaults** row sits 4 px from the left edge, as its layout always intended, rather than 8. - The caption column is as wide as the widest caption instead of a fixed width, diff --git a/docs/TESTS.md b/docs/TESTS.md index 9030415..8995df1 100644 --- a/docs/TESTS.md +++ b/docs/TESTS.md @@ -514,7 +514,7 @@ Tests the branded theme and the stored theme choice. |------|---------| | `TestGoSentryThemeBrandColors` | Verifies the brand colors land on the semantically correct `ColorName`s in both the light and dark variants. | | `TestGoSentryThemeDelegatesUnbrandedColors` | Verifies unbranded color names fall through to the base theme rather than rendering transparent. | -| `TestThemeForChoice` | Verifies the GoSentry choice yields the branded primary and every other value — including the empty legacy one — yields the default theme. | +| `TestThemeForChoice` | Verifies the GoSentry choice and the empty legacy value yield the branded primary; only the explicit default choice yields Fyne's built-in theme. | | `TestThemeLabelRoundTrip` | Verifies the dropdown labels round-trip and that the empty value maps to the Default label rather than a blank option. | --- diff --git a/src/app/operations.go b/src/app/operations.go index 928ca19..a6cb9eb 100644 --- a/src/app/operations.go +++ b/src/app/operations.go @@ -481,8 +481,8 @@ func validateConfig(config domain.Config) error { if config.DefaultTimeoutSeconds < 0 { return errors.New("default timeout must not be negative (0 means no timeout)") } - // Empty Theme is accepted and normalized to the default on load, so older - // configs (and hand-built ones) stay valid without an explicit theme. + // Empty Theme is accepted and normalized to the branded theme on load, so + // older configs (and hand-built ones) stay valid without an explicit theme. if config.Theme != "" && config.Theme != domain.ThemeDefault && config.Theme != domain.ThemeGoSentry { return errors.New("theme must be 'default' or 'gosentry'") } diff --git a/src/domain/config.go b/src/domain/config.go index b03fafe..bfb1643 100644 --- a/src/domain/config.go +++ b/src/domain/config.go @@ -87,8 +87,8 @@ type Config struct { // omitempty would hide a deliberate choice from the hand-editable config. DefaultTimeoutSeconds int `json:"default_timeout_seconds"` Paused bool `json:"paused,omitempty"` - // Theme selects the visual appearance. Empty is treated as ThemeDefault so - // configs written before this field existed keep the original look. + // Theme selects the visual appearance. Empty is treated as ThemeGoSentry so + // configs written before this field existed pick up the branded look. Theme Theme `json:"theme,omitempty"` // JobListView selects the Jobs list density. Empty is treated as // JobListViewDetailed so configs written before this field existed keep the @@ -110,7 +110,7 @@ func DefaultConfig() Config { NotifyOnFailure: true, ExecutionMode: ExecutionModeParallel, OverlapPolicy: OverlapPolicySkip, - Theme: ThemeDefault, + Theme: ThemeGoSentry, JobListView: JobListViewDetailed, DefaultTimeoutSeconds: 0, } diff --git a/src/storage/store.go b/src/storage/store.go index 0e6f627..3c87190 100644 --- a/src/storage/store.go +++ b/src/storage/store.go @@ -118,7 +118,7 @@ func loadOrCreateConfig(paths Paths) (domain.Config, error) { // the setting impossible to persist. Negative values are rejected by // app.validateConfig before they can be saved. if config.Theme == "" { - config.Theme = domain.ThemeDefault + config.Theme = domain.ThemeGoSentry } return config, nil } diff --git a/src/storage/store_test.go b/src/storage/store_test.go index 7563a7b..905f77f 100644 --- a/src/storage/store_test.go +++ b/src/storage/store_test.go @@ -171,8 +171,8 @@ func TestLoadOrCreateConfigCreatesDefaultsOnFirstRun(t *testing.T) { if got.DefaultTimeoutSeconds != 0 { t.Errorf("default DefaultTimeoutSeconds = %d, want 0 (no timeout)", got.DefaultTimeoutSeconds) } - if got.Theme != domain.ThemeDefault { - t.Errorf("default Theme = %q, want %q", got.Theme, domain.ThemeDefault) + if got.Theme != domain.ThemeGoSentry { + t.Errorf("default Theme = %q, want %q", got.Theme, domain.ThemeGoSentry) } if got.JobListView != domain.JobListViewDetailed { t.Errorf("default JobListView = %q, want %q", got.JobListView, domain.JobListViewDetailed) diff --git a/src/ui/settings_view.go b/src/ui/settings_view.go index caa40cf..52f8110 100644 --- a/src/ui/settings_view.go +++ b/src/ui/settings_view.go @@ -263,10 +263,10 @@ const ( ) func themeLabel(choice domain.Theme) string { - if choice == domain.ThemeGoSentry { - return themeLabelGoSentry + if choice == domain.ThemeDefault { + return themeLabelDefault } - return themeLabelDefault + return themeLabelGoSentry } func themeFromLabel(label string) domain.Theme { diff --git a/src/ui/theme.go b/src/ui/theme.go index e2e80d4..2e382ff 100644 --- a/src/ui/theme.go +++ b/src/ui/theme.go @@ -103,14 +103,14 @@ func (t gosentryTheme) Font(style fyne.TextStyle) fyne.Resource { return t.base. func (t gosentryTheme) Icon(name fyne.ThemeIconName) fyne.Resource { return t.base.Icon(name) } func (t gosentryTheme) Size(name fyne.ThemeSizeName) float32 { return t.base.Size(name) } -// themeFor maps a stored Theme choice to a concrete fyne.Theme. Anything other -// than the explicit GoSentry choice (including the empty/legacy value) keeps -// Fyne's built-in theme. +// themeFor maps a stored Theme choice to a concrete fyne.Theme. Only the +// explicit default choice keeps Fyne's built-in theme; everything else +// (including the empty/legacy value) uses the branded GoSentry theme. func themeFor(choice domain.Theme) fyne.Theme { - if choice == domain.ThemeGoSentry { - return newGoSentryTheme() + if choice == domain.ThemeDefault { + return theme.DefaultTheme() } - return theme.DefaultTheme() + return newGoSentryTheme() } // applyTheme installs the theme for the given choice on the running app. Fyne diff --git a/src/ui/theme_test.go b/src/ui/theme_test.go index 8b545fa..723d58a 100644 --- a/src/ui/theme_test.go +++ b/src/ui/theme_test.go @@ -54,24 +54,24 @@ func TestGoSentryThemeDelegatesUnbrandedColors(t *testing.T) { } } -// themeFor maps the stored choice to the right theme: the GoSentry choice yields -// the branded teal primary; every other value (including the empty legacy value) -// yields the default theme, whose primary is not the brand teal. +// themeFor maps the stored choice to the right theme: the GoSentry choice and the +// empty legacy value yield the branded teal primary; only the explicit default +// choice yields Fyne's built-in theme. func TestThemeForChoice(t *testing.T) { - gosentry := themeFor(domain.ThemeGoSentry) - if got := gosentry.Color(theme.ColorNamePrimary, theme.VariantLight); got != brandTeal { - t.Errorf("themeFor(gosentry) primary = %v, want brand teal %v", got, brandTeal) - } - for _, choice := range []domain.Theme{domain.ThemeDefault, ""} { - def := themeFor(choice) - if got := def.Color(theme.ColorNamePrimary, theme.VariantLight); got == brandTeal { - t.Errorf("themeFor(%q) should not use the brand teal primary", choice) + for _, choice := range []domain.Theme{domain.ThemeGoSentry, ""} { + branded := themeFor(choice) + if got := branded.Color(theme.ColorNamePrimary, theme.VariantLight); got != brandTeal { + t.Errorf("themeFor(%q) primary = %v, want brand teal %v", choice, got, brandTeal) } } + def := themeFor(domain.ThemeDefault) + if got := def.Color(theme.ColorNamePrimary, theme.VariantLight); got == brandTeal { + t.Errorf("themeFor(default) should not use the brand teal primary") + } } // The dropdown label helpers must round-trip, and the empty/legacy value must map -// to the Default label so the select never shows a blank option. +// to the GoSentry label so the select never shows a blank option. func TestThemeLabelRoundTrip(t *testing.T) { if got := themeFromLabel(themeLabel(domain.ThemeGoSentry)); got != domain.ThemeGoSentry { t.Errorf("round-trip gosentry = %q", got) @@ -79,7 +79,7 @@ func TestThemeLabelRoundTrip(t *testing.T) { if got := themeFromLabel(themeLabel(domain.ThemeDefault)); got != domain.ThemeDefault { t.Errorf("round-trip default = %q", got) } - if got := themeLabel(""); got != themeLabelDefault { - t.Errorf("empty theme label = %q, want %q", got, themeLabelDefault) + if got := themeLabel(""); got != themeLabelGoSentry { + t.Errorf("empty theme label = %q, want %q", got, themeLabelGoSentry) } }