Define Manager with Set/Status in autostart.go; add concrete types
(windowsManager, linuxManager, otherManager) in the per-platform files.
Service gains a manager field wired by Open() via autostart.New();
AutostartStatus and ApplyAutostart delegate to it instead of calling
package-level functions directly. Tests that don't need autostart get a
nil manager, which is safe (no-op returns).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
src/ui now imports only src/app, src/domain, and Fyne.
Three violations were fixed:
- src/scheduler (NewRealClock): Service.Start() now creates the real
clock internally; StartWith(clock) is the injectable seam for tests.
- src/platform/desktop (InstallDesktopIntegration): moved into
Service.InstallDesktopIcon in new src/app/platform.go.
- src/platform/autostart (AutostartStatus, SetAutostart): moved into
Service.AutostartStatus and Service.ApplyAutostart in platform.go.
go test -race ./... green.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The scheduler no longer shares a *[]domain.Job with the GUI. It is now a
thin timing loop with an injected Clock that calls a tick callback; the
application service is the sole writer of job and runtime state.
- scheduler: add Clock interface + RealClock (clock.go); strip all job
logic from scheduler.go (NewScheduler(clock, tick)); rewrite tests to
cover the loop with a fake clock.
- app.Service: add RunDue(now) (pause + one-run-per-tick policy, records
back through the service) and Start(Clock)/Stop() owning a cancelable
run context; prime each job's first next-run at construction. Capture
the run context under the lock for executeRun.
- gui: talk only to app.Service (no shared state) — Open() the service,
keep a refreshed snapshot, route every mutation through the service,
and react to changes via a single Subscribe listener.
- Tests: add RunDue (due/not-due/paused) and Start-drives-RunDue cases.
Verified with CGO + MSYS2 UCRT64: go vet ./... clean, go test -race
./... green (GUI included), full module builds.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add src/app/operations.go with the seven intents that make the Service
the sole writer of job and runtime state: CreateJob, UpdateJob,
DeleteJob, SetEnabled, RunNow, SetGlobalPause, UpdateSettings. Each
returns error, persists through the store, and announces changes via
RunRecorded/JobChanged/SchedulerStateChanged events.
Extend the Service with a parsed-schedule cache, a global paused flag,
an injectable runJob seam (defaults to runner.RunJob) for testing the
run-now path, and a lifecycle ctx. Run and next-run timing now live in
the Service (duplicating the scheduler temporarily); T3.4 converts the
scheduler to drive the Service and removes the duplication.
Autostart is left to the caller until T5.2's injectable Manager; async
save errors in the run goroutine remain deferred to T5.1. Adds 12 tests
covering create/update/delete, enable/pause, global pause, run-now with
a fake runner, and settings persistence/validation.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add src/app/events.go: a sealed Event interface with three concrete
types (JobChanged, RunRecorded, SchedulerStateChanged), an Observer
interface plus ObserverFunc adapter, and Subscribe/emit on the Service.
This replaces the scheduler's single onChange callback with typed
events the UI can exhaustively type-switch over.
Dispatch is serialized by a dedicated dispatchMu (separate from the
state lock): observers never run concurrently, emit must be called
without holding s.mu so observers can read Service state, and observers
must not re-enter an emitting method. emit is wired to mutating ops in
T3.3. Adds tests for ordered multi-observer delivery, empty-observer
no-op, and observer-reads-state-without-deadlock.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Create src/app/service.go: the application-service layer that becomes
the single owner of the durable jobs slice and the transient runtime
map, guarded by a non-reentrant sync.Mutex. NewService wires a loaded
store; Open() is the convenience entry point. Read-only accessors
(Jobs/Runtime/Store) take the lock, and Jobs() returns a copy to keep
callers from mutating Service-owned state.
State-mutating intents and the event/observer machinery are deferred to
T3.2-T3.4. Adds no-Fyne unit tests for runtime construction, copy
isolation, and store wiring.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>