Make the branded GoSentry theme the default.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-08-04 19:18:48 +03:00
parent e482e3261c
commit 276539c383
10 changed files with 37 additions and 33 deletions
+1 -1
View File
@@ -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"
}
```
+4
View File
@@ -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,
+1 -1
View File
@@ -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. |
---
+2 -2
View File
@@ -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'")
}
+3 -3
View File
@@ -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,
}
+1 -1
View File
@@ -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
}
+2 -2
View File
@@ -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)
+3 -3
View File
@@ -263,11 +263,11 @@ const (
)
func themeLabel(choice domain.Theme) string {
if choice == domain.ThemeGoSentry {
return themeLabelGoSentry
}
if choice == domain.ThemeDefault {
return themeLabelDefault
}
return themeLabelGoSentry
}
func themeFromLabel(label string) domain.Theme {
if label == themeLabelGoSentry {
+6 -6
View File
@@ -103,15 +103,15 @@ 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 newGoSentryTheme()
}
// applyTheme installs the theme for the given choice on the running app. Fyne
// refreshes every canvas when the theme changes, so this works both at startup
+13 -13
View File
@@ -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.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)
}
for _, choice := range []domain.Theme{domain.ThemeDefault, ""} {
def := themeFor(choice)
}
def := themeFor(domain.ThemeDefault)
if got := def.Color(theme.ColorNamePrimary, theme.VariantLight); got == brandTeal {
t.Errorf("themeFor(%q) should not use the brand teal primary", choice)
}
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)
}
}