fix: accept zero retention limits, retire Store() for typed accessors

Phase 8 (PROJECT_REVIEW_PLAN.md 8.1): 0 in MaxLogFiles/MaxLogAgeDays now
means "keep everything" end to end. runner.CleanupLogs already treated
<= 0 as disabled; validateConfig, the Settings form, and
loadOrCreateConfig's backfill were the only things making that state
unreachable.

Phase 9 (1.1, rolling up 1.2, 1.3, 7.3): added Service.Config() and
Service.Paths(), copying under mu, and converted every UI site that read
Service state through the raw *storage.Store returned by Store() (now
removed). jobs_view's pause control is now driven by refreshView reading
svc.Config().Paused on every event instead of only mirroring its own tap
handler, which makes it an actual consumer of SchedulerStateChanged.
mainwindow's event listener is a real type switch, and events.go's doc
comment no longer claims a compiler exhaustiveness check Go doesn't have.
Unexported the redundant SetAutostart/AutostartStatus package functions
in platform/autostart now that only the Manager methods are used outside
the package.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-06 22:15:19 +03:00
parent 0c8442a8d1
commit ca2a8c8aa7
18 changed files with 223 additions and 115 deletions
+21 -5
View File
@@ -204,11 +204,27 @@ func Open() (*Service, error) {
return svc, nil
}
// Store returns the underlying store. It is exposed so callers that still need
// resolved paths and config (the GUI, during the transition) can reach them;
// later phases narrow this surface.
func (s *Service) Store() *storage.Store {
return s.store
// Config returns a copy of the current application configuration, safe to
// call from any goroutine. UpdateSettings, SetGlobalPause, and SetJobListView
// are the only writers and all mutate store.Config under mu; copying under the
// same lock is what keeps a UI read from racing them, instead of holding onto
// the *storage.Store this used to hand out (see STANDARDS: the UI reads
// Service state through typed events and accessors, never shared mutable
// state).
func (s *Service) Config() domain.Config {
s.mu.Lock()
defer s.mu.Unlock()
return s.store.Config
}
// Paths returns a copy of the store's resolved filesystem paths. AppDir and
// ConfigPath are fixed for the process; JobsPath, JobsDir, and LogsDir are
// re-derived under mu on every settings save (storage.Store.applyConfigPaths),
// so this copies under the same lock as Config for the same reason.
func (s *Service) Paths() storage.Paths {
s.mu.Lock()
defer s.mu.Unlock()
return s.store.Paths
}
// Jobs returns a copy of the durable jobs slice. Returning a copy keeps callers