feat: add a compact job list view to the Jobs tab

Each job can now render as a single line — name on the left, status on the
right — instead of the three-line block, so many more jobs fit without
scrolling. A toggle button beside the Folder filter switches between the two
modes and is labelled with the action it performs, matching the existing
"Disable auto" convention.

The choice is persisted as Config.JobListView ("detailed" / "compact", stored
as job_list_view in gosentry.json). Empty, legacy, and unrecognised values all
normalize to detailed, so existing installs keep the current look and the file
never gains a value no reader understands.

Selection, the details panel, the folder filter, and live status updates work
unchanged in both modes.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
mixeme
2026-07-26 22:14:06 +03:00
parent e85cbc4eb1
commit 29ce94c3e8
11 changed files with 433 additions and 9 deletions
+25
View File
@@ -28,6 +28,26 @@ const (
ThemeGoSentry Theme = "gosentry"
)
// JobListView selects how densely the Jobs tab renders its sidebar list. Like
// Theme it is a UI-only choice with no effect on scheduling; it lives in Config
// so the user's preference survives a restart.
type JobListView string
const (
// JobListViewDetailed is the three-line row: name, metadata, status.
JobListViewDetailed JobListView = "detailed"
// JobListViewCompact is the one-line row: name on the left, status on the
// right, so many more jobs fit without scrolling.
JobListViewCompact JobListView = "compact"
)
// IsCompact reports whether the compact rendering is selected. Only the exact
// "compact" value counts, so empty, legacy, and unrecognised values all read as
// detailed — every consumer normalizes them the same way.
func (v JobListView) IsCompact() bool {
return v == JobListViewCompact
}
// OverlapPolicy decides what happens when a job's next run fires while the
// previous run is still active.
type OverlapPolicy string
@@ -62,6 +82,10 @@ type Config struct {
// Theme selects the visual appearance. Empty is treated as ThemeDefault so
// configs written before this field existed keep the original 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
// current three-line rows.
JobListView JobListView `json:"job_list_view,omitempty"`
}
// DefaultConfig returns the built-in default settings. It is the config used
@@ -79,6 +103,7 @@ func DefaultConfig() Config {
ExecutionMode: ExecutionModeParallel,
OverlapPolicy: OverlapPolicySkip,
Theme: ThemeDefault,
JobListView: JobListViewDetailed,
DefaultTimeoutSeconds: 0,
}
}
+30
View File
@@ -0,0 +1,30 @@
package domain
import "testing"
// TestJobListViewIsCompact pins the normalization rule: only the exact
// "compact" value selects the one-line rows, so empty and unrecognised values
// (including configs written before the field existed) keep the detailed look.
func TestJobListViewIsCompact(t *testing.T) {
cases := []struct {
view JobListView
want bool
}{
{JobListViewCompact, true},
{JobListViewDetailed, false},
{"", false},
{"Compact", false},
{"tiny", false},
}
for _, tc := range cases {
if got := tc.view.IsCompact(); got != tc.want {
t.Errorf("JobListView(%q).IsCompact() = %v, want %v", tc.view, got, tc.want)
}
}
}
func TestDefaultConfigUsesDetailedJobList(t *testing.T) {
if got := DefaultConfig().JobListView; got != JobListViewDetailed {
t.Errorf("default JobListView = %q, want %q", got, JobListViewDetailed)
}
}