Files
gosentry/docs/PLAN-compact-job-list.md
T
mixeme c58fe0d332 docs: add implementation plan for compact job list view
Plans an opt-in one-line rendering of the Jobs tab list (name left, status
right) alongside the current three-line detailed rows, switched from a "View"
dropdown beside the Folder filter and persisted as a new Config.JobListView
field.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-26 20:07:06 +03:00

7.0 KiB

Implementation plan — compact / detailed job list view

Context

The Jobs tab sidebar renders every job as a three-line block (bold name, a metadata line with folder + schedule + command, and a status line) — see the widget.NewList call in jobs_view.go:108. That is informative but tall: with many jobs the sidebar needs constant scrolling.

Add a second, opt-in rendering of the same list: Compact shows one line per job (name left, status right); Detailed is the current three-line block and stays the default. The choice is switchable from the Jobs tab itself and is persisted in gosentry.json, so it survives a restart like Config.Theme does.

Design decisions

  • Control lives in the Jobs sidebar header — a "View" dropdown beside the existing "Folder" filter. Switching is one click, next to what it affects.
  • Compact row = name + status on a single line. Same height as one label, so per-job health stays visible at a glance.
  • One list, one row template. widget.List caches the row template's MinSize as itemMin; list.Refresh() re-creates the template and recomputes it (verified in Fyne 2.7.4 widget/list.go, listRenderer.Refresh), so the rows genuinely shrink/grow on a mode switch without rebuilding the widget. The template therefore always holds the same four objects, and the mode is expressed by Show()/Hide(). Both compactVBoxLayout (src/ui/layout.go) and Fyne's border layout skip hidden children in MinSize, so a hidden line contributes no height.

Changes

1. src/domain/config.go — persisted preference

  • New string type JobListView with JobListViewDetailed = "detailed" and JobListViewCompact = "compact", documented like Theme as a UI-only choice.
  • New field JobListView JobListView \json:"job_list_view,omitempty"`onConfig`; comment that empty means detailed so pre-existing configs keep the current look.
  • DefaultConfig() returns JobListViewDetailed.
  • Method func (v JobListView) IsCompact() bool — only the exact "compact" value is compact — so every consumer normalizes unknown/legacy values the same way.

2. src/app/operations.go — save the choice

New func (s *Service) SetJobListView(view domain.JobListView) error, modelled on SetGlobalPause (src/app/operations.go:168) but deliberately lighter: normalize a non-compact value to JobListViewDetailed, take mu, return early if unchanged, set s.store.Config.JobListView, call s.store.SaveConfig(), unlock. No SaveJobs (no job changed), no event emitted (this is presentational only — the Jobs view refreshes its own list; an event would trigger a pointless whole-window refresh). A comment records that reasoning.

3. src/ui/jobs_view.go — rendering and the toggle

  • Read the initial mode into a compactList local: svc.Store().Config.JobListView.IsCompact().
  • CreateItem: build name (bold, Wrapping = fyne.TextTruncate so a long name cannot push the status off the row), inlineStatus (compact-only, right-hand side), meta, and status. Put the first two on one line via container.NewBorder(nil, nil, nil, inlineStatus, name) and keep the outer compactVBoxLayout{spacing: jobRowSpacing} container with [nameLine, meta, status]. Apply the mode's visibility here too — this is what makes itemMin correct for the mode.
  • UpdateItem: reach nameLine through row.Objects[0].(*fyne.Container) (NewBorder appends the border slots after the center object, so Objects[0] is name and Objects[1] is inlineStatus — worth a comment, matching the existing index-based access in this callback). Set all four texts from the existing helpers (app.DisplayFolder, app.DisplayInvocation, app.StatusText) and re-apply visibility on every update: a full Refresh reuses the already-visible row objects, which were created under the old mode, so visibility cannot be left to CreateItem alone.
  • View dropdown: widget.NewSelect([]string{viewLabelDetailed, viewLabelCompact}, …). Its handler maps label → domain.JobListView, returns early when unchanged, updates compactList, persists via svc.SetJobListView (on error dialog.ShowError and revert the select, per the error rule in docs/STANDARDS.md), then calls list.Refresh(). The selection and the details panel are untouched by the switch.
  • Header layout: replace the current "Folder" caption + folderSelect pair in sidebarHeader with container.NewGridWithColumns(2, <Folder caption + select>, <View caption + select>), so the second dropdown costs no extra header height and the list keeps the same room. The 400px minJobsSidebarWidth comfortably fits two short selects.

4. src/ui/jobs_view_helpers.go — label mapping

viewLabelDetailed/viewLabelCompact consts plus pure viewLabel(domain.JobListView) string and viewFromLabel(string) domain.JobListView, mirroring themeLabel/themeFromLabel (src/ui/settings_view.go:370) so the on-disk strings never reach the user.

5. Tests

  • src/domain/config_test.go (new): JobListView.IsCompact for "compact", "detailed", "", and a junk value.
  • src/ui/jobs_view_test.go: viewLabel/viewFromLabel round-trip, and unknown/empty label → detailed.
  • src/app/operations_test.go: TestSetJobListViewPersistsToConfigFile modelled on TestSetGlobalPausePersistsToConfigFile (src/app/operations_test.go:553) — switch to compact, unmarshal svc.store.Paths.ConfigPath, assert the field, switch back; plus a case asserting an unknown value is stored as detailed.
  • src/storage/store_test.go: one added assertion in the defaults test beside the existing Theme check (~line 174) that a fresh config is detailed.

6. Docs and version

  • docs/CHANGELOG.md: new ## 0.14.0 - 2026-07-26 section (a feature → minor bump) covering the compact/detailed toggle and the new job_list_view field, noting legacy configs stay detailed.
  • src/app/version.go: 0.13.00.14.0.
  • docs/ARCHITECTURE.md: add the two new helpers to the jobs_view_helpers.go row of the file-split table (~line 195).

Verification

  1. Build, vet, and test the whole module through the cgo-enabled shell (the default Bash env has CGO off, so GUI packages will not compile there):
export PATH="/c/msys64/ucrt64/bin:$PATH"; export CGO_ENABLED=1; go build ./... && go vet ./... && go test -race ./...
  1. Run the app (go run ./cmd/gosentry) with several jobs configured and check:
    • Default launch is Detailed and looks exactly as before.
    • Switching to Compact collapses every row to one line, name left / status right, and many more jobs fit without scrolling.
    • Selection and the details panel keep working after switching, in both directions, including with a folder filter active and with an empty list.
    • A running job's status still updates live in compact rows.
    • gosentry.json gains "job_list_view": "compact"; restarting reopens in compact, and switching back removes/flips the field.