Rename the Default theme option to System.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -33,7 +33,7 @@ creating, grouping, pausing, running, and monitoring scheduled shell commands.
|
|||||||
- Desktop notifications on job failure.
|
- Desktop notifications on job failure.
|
||||||
- Windows tray icon: left-click to show the window, right-click for the menu.
|
- Windows tray icon: left-click to show the window, right-click for the menu.
|
||||||
- Autostart on login (Windows shortcut; Linux XDG desktop entry).
|
- Autostart on login (Windows shortcut; Linux XDG desktop entry).
|
||||||
- Detailed or compact job list, and a default or branded theme; both are remembered.
|
- Detailed or compact job list, and a system or branded theme; both are remembered.
|
||||||
|
|
||||||
## Platforms
|
## Platforms
|
||||||
|
|
||||||
@@ -83,7 +83,7 @@ That is the file GoSentry writes on first run. `default_timeout_seconds` is the
|
|||||||
run timeout applied to jobs that do not set their own; `0` means no timeout, and
|
run timeout applied to jobs that do not set their own; `0` means no timeout, and
|
||||||
it is written out even though it is zero, because a missing value and a
|
it is written out even though it is zero, because a missing value and a
|
||||||
deliberate "no timeout" have to stay distinguishable in a hand-edited file.
|
deliberate "no timeout" have to stay distinguishable in a hand-edited file.
|
||||||
`theme` is `default` or `gosentry` (the branded teal/amber look), and
|
`theme` is `system` or `gosentry` (the branded teal/amber look), and
|
||||||
`job_list_view` is `detailed` or `compact` — both are remembered from the
|
`job_list_view` is `detailed` or `compact` — both are remembered from the
|
||||||
choices made in the app. Keys left at their off value (`start_on_login`,
|
choices made in the app. Keys left at their off value (`start_on_login`,
|
||||||
`paused`) are omitted until they are turned on.
|
`paused`) are omitted until they are turned on.
|
||||||
|
|||||||
+3
-1
@@ -40,8 +40,10 @@ dragged.**
|
|||||||
|
|
||||||
- **The branded GoSentry theme is now the default.** Fresh installs, the
|
- **The branded GoSentry theme is now the default.** Fresh installs, the
|
||||||
**Defaults** button, and configs that omit `theme` all open in the teal/amber
|
**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
|
look; users who prefer Fyne's built-in theme can still pick **System** in
|
||||||
Settings.
|
Settings.
|
||||||
|
- The Fyne built-in theme option is labelled **System** (stored as `"system"`);
|
||||||
|
configs that still say `"default"` are read as System and rewritten on save.
|
||||||
- The **Save / Cancel / Restore defaults** row sits 4 px from the left edge, as
|
- The **Save / Cancel / Restore defaults** row sits 4 px from the left edge, as
|
||||||
its layout always intended, rather than 8.
|
its layout always intended, rather than 8.
|
||||||
- The caption column is as wide as the widest caption instead of a fixed width,
|
- The caption column is as wide as the widest caption instead of a fixed width,
|
||||||
|
|||||||
+3
-2
@@ -237,6 +237,7 @@ Tests JSON round-tripping, default generation, and backward compatibility.
|
|||||||
| `TestLoadOrCreateConfigCreatesDefaultsOnFirstRun` | Verifies that a missing config file is created with sane defaults and a sample job. |
|
| `TestLoadOrCreateConfigCreatesDefaultsOnFirstRun` | Verifies that a missing config file is created with sane defaults and a sample job. |
|
||||||
| `TestLoadOrCreateConfigKeepsZeroTimeoutOnReload` | Verifies that `default_timeout_seconds: 0` survives a reload rather than being normalized away — 0 is a value, not a missing field. |
|
| `TestLoadOrCreateConfigKeepsZeroTimeoutOnReload` | Verifies that `default_timeout_seconds: 0` survives a reload rather than being normalized away — 0 is a value, not a missing field. |
|
||||||
| `TestLoadOrCreateConfigMigratesJobsDir` | Verifies that a pre-0.15 `jobs_dir` becomes `jobs_file` pointing at the same `jobs.json`, and that the retired key is not written back. |
|
| `TestLoadOrCreateConfigMigratesJobsDir` | Verifies that a pre-0.15 `jobs_dir` becomes `jobs_file` pointing at the same `jobs.json`, and that the retired key is not written back. |
|
||||||
|
| `TestLoadOrCreateConfigMigratesLegacyThemeDefault` | Verifies that a config storing the retired `"default"` theme value is normalized to `system` on load. |
|
||||||
| `TestLoadJobsFileReportsMissingWithoutCreating` | Verifies that `LoadJobsFile` reports a missing file as not-found without creating or seeding it, and normalizes the jobs it does load. |
|
| `TestLoadJobsFileReportsMissingWithoutCreating` | Verifies that `LoadJobsFile` reports a missing file as not-found without creating or seeding it, and normalizes the jobs it does load. |
|
||||||
| `TestApplyConfigPathsDerivesJobsDir` | Verifies that the configured jobs file resolves against the program folder and that `Paths.JobsDir` is derived from it. |
|
| `TestApplyConfigPathsDerivesJobsDir` | Verifies that the configured jobs file resolves against the program folder and that `Paths.JobsDir` is derived from it. |
|
||||||
| `TestJobTimeoutRoundTripsThreeStates` | Verifies the on-disk encoding that keeps "inherit" and "no timeout" distinguishable: `nil` is omitted entirely, an explicit `0` is written and read back as set. |
|
| `TestJobTimeoutRoundTripsThreeStates` | Verifies the on-disk encoding that keeps "inherit" and "no timeout" distinguishable: `nil` is omitted entirely, an explicit `0` is written and read back as set. |
|
||||||
@@ -514,8 +515,8 @@ 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. |
|
| `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. |
|
| `TestGoSentryThemeDelegatesUnbrandedColors` | Verifies unbranded color names fall through to the base theme rather than rendering transparent. |
|
||||||
| `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. |
|
| `TestThemeForChoice` | Verifies the GoSentry choice and the empty legacy value yield the branded primary; only the explicit system 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. |
|
| `TestThemeLabelRoundTrip` | Verifies the dropdown labels round-trip and that the empty value maps to the GoSentry label rather than a blank option. |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -483,8 +483,8 @@ func validateConfig(config domain.Config) error {
|
|||||||
}
|
}
|
||||||
// Empty Theme is accepted and normalized to the branded theme on load, so
|
// 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.
|
// older configs (and hand-built ones) stay valid without an explicit theme.
|
||||||
if config.Theme != "" && config.Theme != domain.ThemeDefault && config.Theme != domain.ThemeGoSentry {
|
if config.Theme != "" && config.Theme != domain.ThemeSystem && config.Theme != domain.ThemeGoSentry {
|
||||||
return errors.New("theme must be 'default' or 'gosentry'")
|
return errors.New("theme must be 'system' or 'gosentry'")
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,8 +21,8 @@ const (
|
|||||||
type Theme string
|
type Theme string
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// ThemeDefault keeps Fyne's built-in theme — the original look.
|
// ThemeSystem keeps Fyne's built-in theme, following the platform look.
|
||||||
ThemeDefault Theme = "default"
|
ThemeSystem Theme = "system"
|
||||||
// ThemeGoSentry applies the branded teal/amber theme derived from the logo
|
// ThemeGoSentry applies the branded teal/amber theme derived from the logo
|
||||||
// and app icon.
|
// and app icon.
|
||||||
ThemeGoSentry Theme = "gosentry"
|
ThemeGoSentry Theme = "gosentry"
|
||||||
|
|||||||
@@ -120,6 +120,9 @@ func loadOrCreateConfig(paths Paths) (domain.Config, error) {
|
|||||||
if config.Theme == "" {
|
if config.Theme == "" {
|
||||||
config.Theme = domain.ThemeGoSentry
|
config.Theme = domain.ThemeGoSentry
|
||||||
}
|
}
|
||||||
|
if config.Theme == "default" {
|
||||||
|
config.Theme = domain.ThemeSystem
|
||||||
|
}
|
||||||
return config, nil
|
return config, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -183,6 +183,34 @@ func TestLoadOrCreateConfigCreatesDefaultsOnFirstRun(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestLoadOrCreateConfigMigratesLegacyThemeDefault covers a gosentry.json that
|
||||||
|
// still stores the retired "default" theme value: load normalizes it to system.
|
||||||
|
func TestLoadOrCreateConfigMigratesLegacyThemeDefault(t *testing.T) {
|
||||||
|
dir := t.TempDir()
|
||||||
|
paths := Paths{
|
||||||
|
AppDir: dir,
|
||||||
|
ConfigPath: filepath.Join(dir, ConfigFileName),
|
||||||
|
}
|
||||||
|
legacy := map[string]any{
|
||||||
|
"jobs_file": "jobs.json",
|
||||||
|
"logs_dir": "logs",
|
||||||
|
"max_log_files": 100,
|
||||||
|
"max_log_age_days": 30,
|
||||||
|
"theme": "default",
|
||||||
|
}
|
||||||
|
if err := writeJSON(paths.ConfigPath, legacy); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
got, err := loadOrCreateConfig(paths)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if got.Theme != domain.ThemeSystem {
|
||||||
|
t.Errorf("migrated Theme = %q, want %q", got.Theme, domain.ThemeSystem)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// TestLoadOrCreateConfigKeepsZeroTimeoutOnReload guards the "0 = no timeout"
|
// TestLoadOrCreateConfigKeepsZeroTimeoutOnReload guards the "0 = no timeout"
|
||||||
// setting against being normalized away when an existing gosentry.json is read
|
// setting against being normalized away when an existing gosentry.json is read
|
||||||
// back. Loading must not treat 0 as a missing value.
|
// back. Loading must not treat 0 as a missing value.
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ func settingsView(w fyne.Window, svc *app.Service) fyne.CanvasObject {
|
|||||||
notifications := widget.NewCheck("Show desktop notifications for failed jobs", nil)
|
notifications := widget.NewCheck("Show desktop notifications for failed jobs", nil)
|
||||||
notifications.SetChecked(store.Config.NotifyOnFailure)
|
notifications.SetChecked(store.Config.NotifyOnFailure)
|
||||||
notifications.OnChanged = func(bool) { updateSaveState() }
|
notifications.OnChanged = func(bool) { updateSaveState() }
|
||||||
themeSelect := widget.NewSelect([]string{themeLabelDefault, themeLabelGoSentry}, nil)
|
themeSelect := widget.NewSelect([]string{themeLabelSystem, themeLabelGoSentry}, nil)
|
||||||
themeSelect.SetSelected(themeLabel(store.Config.Theme))
|
themeSelect.SetSelected(themeLabel(store.Config.Theme))
|
||||||
// Preview the theme the moment it is picked so the choice is visible before
|
// Preview the theme the moment it is picked so the choice is visible before
|
||||||
// saving; Save persists it. Reverting the selection reverts the preview, and
|
// saving; Save persists it. Reverting the selection reverts the preview, and
|
||||||
@@ -256,15 +256,15 @@ func settingsView(w fyne.Window, svc *app.Service) fyne.CanvasObject {
|
|||||||
|
|
||||||
// Theme dropdown labels. These are the human-facing captions; themeLabel and
|
// Theme dropdown labels. These are the human-facing captions; themeLabel and
|
||||||
// themeFromLabel translate between them and the stored domain.Theme values so the
|
// themeFromLabel translate between them and the stored domain.Theme values so the
|
||||||
// select never leaks the on-disk "default"/"gosentry" strings to the user.
|
// select never leaks the on-disk "system"/"gosentry" strings to the user.
|
||||||
const (
|
const (
|
||||||
themeLabelDefault = "Default"
|
themeLabelSystem = "System"
|
||||||
themeLabelGoSentry = "GoSentry"
|
themeLabelGoSentry = "GoSentry"
|
||||||
)
|
)
|
||||||
|
|
||||||
func themeLabel(choice domain.Theme) string {
|
func themeLabel(choice domain.Theme) string {
|
||||||
if choice == domain.ThemeDefault {
|
if choice == domain.ThemeSystem {
|
||||||
return themeLabelDefault
|
return themeLabelSystem
|
||||||
}
|
}
|
||||||
return themeLabelGoSentry
|
return themeLabelGoSentry
|
||||||
}
|
}
|
||||||
@@ -273,5 +273,5 @@ func themeFromLabel(label string) domain.Theme {
|
|||||||
if label == themeLabelGoSentry {
|
if label == themeLabelGoSentry {
|
||||||
return domain.ThemeGoSentry
|
return domain.ThemeGoSentry
|
||||||
}
|
}
|
||||||
return domain.ThemeDefault
|
return domain.ThemeSystem
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-2
@@ -104,10 +104,10 @@ func (t gosentryTheme) Icon(name fyne.ThemeIconName) fyne.Resource { return t.ba
|
|||||||
func (t gosentryTheme) Size(name fyne.ThemeSizeName) float32 { return t.base.Size(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. Only the
|
// themeFor maps a stored Theme choice to a concrete fyne.Theme. Only the
|
||||||
// explicit default choice keeps Fyne's built-in theme; everything else
|
// explicit system choice keeps Fyne's built-in theme; everything else
|
||||||
// (including the empty/legacy value) uses the branded GoSentry theme.
|
// (including the empty/legacy value) uses the branded GoSentry theme.
|
||||||
func themeFor(choice domain.Theme) fyne.Theme {
|
func themeFor(choice domain.Theme) fyne.Theme {
|
||||||
if choice == domain.ThemeDefault {
|
if choice == domain.ThemeSystem {
|
||||||
return theme.DefaultTheme()
|
return theme.DefaultTheme()
|
||||||
}
|
}
|
||||||
return newGoSentryTheme()
|
return newGoSentryTheme()
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ func TestGoSentryThemeDelegatesUnbrandedColors(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// themeFor maps the stored choice to the right theme: the GoSentry choice and the
|
// 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
|
// empty legacy value yield the branded teal primary; only the explicit system
|
||||||
// choice yields Fyne's built-in theme.
|
// choice yields Fyne's built-in theme.
|
||||||
func TestThemeForChoice(t *testing.T) {
|
func TestThemeForChoice(t *testing.T) {
|
||||||
for _, choice := range []domain.Theme{domain.ThemeGoSentry, ""} {
|
for _, choice := range []domain.Theme{domain.ThemeGoSentry, ""} {
|
||||||
@@ -64,9 +64,9 @@ func TestThemeForChoice(t *testing.T) {
|
|||||||
t.Errorf("themeFor(%q) primary = %v, want brand teal %v", choice, got, brandTeal)
|
t.Errorf("themeFor(%q) primary = %v, want brand teal %v", choice, got, brandTeal)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
def := themeFor(domain.ThemeDefault)
|
sys := themeFor(domain.ThemeSystem)
|
||||||
if got := def.Color(theme.ColorNamePrimary, theme.VariantLight); got == brandTeal {
|
if got := sys.Color(theme.ColorNamePrimary, theme.VariantLight); got == brandTeal {
|
||||||
t.Errorf("themeFor(default) should not use the brand teal primary")
|
t.Errorf("themeFor(system) should not use the brand teal primary")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -76,8 +76,8 @@ func TestThemeLabelRoundTrip(t *testing.T) {
|
|||||||
if got := themeFromLabel(themeLabel(domain.ThemeGoSentry)); got != domain.ThemeGoSentry {
|
if got := themeFromLabel(themeLabel(domain.ThemeGoSentry)); got != domain.ThemeGoSentry {
|
||||||
t.Errorf("round-trip gosentry = %q", got)
|
t.Errorf("round-trip gosentry = %q", got)
|
||||||
}
|
}
|
||||||
if got := themeFromLabel(themeLabel(domain.ThemeDefault)); got != domain.ThemeDefault {
|
if got := themeFromLabel(themeLabel(domain.ThemeSystem)); got != domain.ThemeSystem {
|
||||||
t.Errorf("round-trip default = %q", got)
|
t.Errorf("round-trip system = %q", got)
|
||||||
}
|
}
|
||||||
if got := themeLabel(""); got != themeLabelGoSentry {
|
if got := themeLabel(""); got != themeLabelGoSentry {
|
||||||
t.Errorf("empty theme label = %q, want %q", got, themeLabelGoSentry)
|
t.Errorf("empty theme label = %q, want %q", got, themeLabelGoSentry)
|
||||||
|
|||||||
Reference in New Issue
Block a user