The "Selected job activity" list kept showing the previous job's entries
when a different job was selected. dp.update() reassigned the backing
slice but never refreshed the widget.List, and only the refreshView path
appended an explicit refresh. Move d.logs.Refresh() into update()/clear()
so every caller redraws the panel.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Bump version to 0.10.2 and document the UI density/resizing work in the
changelog. Also restructure the settings form into sections so separators
and the editable Storage fields keep normal spacing (dividers no longer
crowd the row above, entry boxes stay visibly separated) while the
label-only sections remain condensed.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Make the job details and settings views more compact and remove the tall
minimum that prevented resizing the window shorter:
- Lay job metadata out in two columns and stack rows with a negative-gap
compactVBoxLayout, roughly halving the block height.
- Wrap the settings form in a vertical scroll so it no longer dictates
the window minimum (AppTabs sizes to the tallest tab), and tighten its
rows with the same compact layout.
- Shrink the command-output scroll minimum height so the details pane can
get shorter; long output still scrolls.
- Size the "Selected job activity" panel to exactly maxJobActivityRows
using widget.List's own content-height formula, so all three rows show
without a scrollbar regardless of theme or DPI.
Together these drop the minimum window height from ~891px to ~570px.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
T6.1: split jobs_view.go into three files — jobs_view_helpers.go (pure
helpers) and jobs_view_details.go (detailsPanel struct with widget
creation, update, clear, and container methods) — bringing jobs_view.go
from 459 to ~200 lines.
T6.2: remove stale YAML upgrade note from README; drop *.yaml from
.dockerignore.
T6.3: delete YAML shadow structs (yamlConfig/yamlJob/yamlJobsFile),
importYAMLConfig/importYAMLJobs, legacy path constants, and all
YAML-import tests; run go mod tidy to remove go.yaml.in/yaml/v4.
T6.4: refresh ARCHITECTURE.md — JSON storage references, new Key Domain
Concepts section (per-job overlap policy, run-time statistics + log
seeding, persisted pause flag, jobs_view split).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Lower default from 1120×720 to 1024×660 so the window opens fully
visible on 1366×768 / 720p screens. Reduce minJobsSidebarWidth (480→400)
and commandOutputScroll min size (520×160→460×120) to match; the layout
engine enforces these as the natural minimum, keeping the UI usable at
the smaller default.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
TestSetGlobalPausePersistsToConfigFile reads gosentry.json back from disk
after each SetGlobalPause call and asserts the Paused flag is correctly
written and cleared. TestServiceRebuiltFromPausedStoreStartsPaused builds
a second Service from the already-updated store and confirms both RunDue
and RunNow are refused while the service is paused.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
schedulerPaused, the scheduler-state label, and the Pause-all/Resume-all
button now read Config.Paused at construction time so they correctly reflect
a paused install on restart instead of always starting in the running state.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add Config.Paused bool so the scheduler's paused flag survives a restart.
SetGlobalPause now writes the flag into store.Config and calls SaveConfig;
NewService seeds s.paused from Config.Paused before computing first next-run
times, so jobs show "Scheduler paused" immediately at startup when paused.
Also add Paused to the yamlConfig shadow struct to keep the direct conversion
from domain.Config valid until the YAML import path is dropped in T6.3.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds three tests for the effective-policy resolution:
- per-job "queue" overrides global "skip" (job marked Pending, re-runs)
- per-job "skip" overrides global "queue" (overlap dropped, no re-run)
- empty Job.OverlapPolicy inherits the global default
Also ticks Phase 3 complete in docs/RELEASE-0.10-TASKS.md.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- job_dialog.go: add overlap-policy widget.Select with "(Use global
default)" → empty, "skip", and "queue" options; pre-selects the job's
current value; on save maps the inherit label back to empty string.
- app/format.go: add DisplayOverlapPolicy(job, globalPolicy) — returns
the policy name when overridden, else "skip/queue (global default)".
- ui/jobs_view.go: add "Overlap policy" detail row updated in
updateDetails using DisplayOverlapPolicy.
- settings_view.go: rename "Overlap policy" label to "Default overlap
policy" to clarify it is the global default jobs can override.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
RunDue now resolves each job's effective overlap policy via
effectiveOverlapPolicy (job value when set, else Config.OverlapPolicy)
instead of reading the global policy once per tick. An empty
Job.OverlapPolicy inherits the global default, so normalizeJobs continues
to leave the field untouched rather than backfilling it.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Adds OverlapPolicy field to Job struct with json:"overlap_policy,omitempty"
tag. When empty, the field signals that the job should inherit the global
overlap policy setting from Config. Also updates yamlJob shadow struct to
maintain field-layout equivalence for YAML→JSON import compatibility.
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
The Log column was fixed at 240px with TextTruncate, clipping log file
names like 20260601-100000_<JobName>.log. Measure the widest value and
size the column to fit (bounded 240–520px), recomputing on refresh so
newly recorded events widen the column instead of being cut off.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Show only the latest 3 activity entries for the selected job instead of the
full in-memory list, and pin the panel to the bottom of the details pane via a
Border so the command output absorbs the remaining vertical space.
Adds fixedHeightLayout (mirroring minWidthLayout) to reserve a stable height
for the activity list. The full per-job history (maxJobLogs) is untouched and
still available in the History view.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add DisplayStats to format.go: returns "No runs recorded" when RunCount is
zero, otherwise a one-line "N runs, M failed, last X ms, avg Y ms, max Z ms"
summary. Wire a Statistics detail row into the jobs panel that refreshes via
updateDetails alongside the other runtime fields.
Tests: TestDisplayStats (nil/zero/normal/no-fail), TestUpdateStats (three
sequential fake runs through updateStats verifying all aggregate fields), and
five seed tests in runner/seed_test.go covering basic aggregation,
duration-less legacy logs, maxFiles capping, missing directory, and
unmatched log files.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add SeedStats, which reconstructs per-job execution-time aggregates from
existing log files: suffix-matched by sanitized job name, bounded by
MaxLogFiles, and tolerant of duration-less legacy logs (counted in
run/fail totals but excluded from last/avg/max). NewService folds the
seed into each JobRuntime at build time so stats survive a restart.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add RunCount, FailCount, LastDurationMS, AvgDurationMS, MaxDurationMS
to JobRuntime and fold each completed RunRecord into them via updateStats
called from executeRun.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Pass durationMS to writeRunLog and include it as 'duration' in the
log header for persistence.
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Add DurationMS int64 field to RunRecord and measure wall-clock
start→finish in RunJob; StartOnly jobs record 0.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Cover the compact formatter with three scenarios:
- Log file present: shows base name only (not full path)
- Log file absent: formats without a log column
- Empty trigger: defaults to "Unknown"
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Switch the jobLogs list template to TextTruncate wrapping and use
EventLine (base log filename only) instead of EventText so each
activity row stays on a single line in the jobs panel.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Uses filepath.Base for the log file so the jobs log view can show
one-line entries without the full directory path cluttering the row.
EventText is kept intact for the History table.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Tests touching SysProcAttr.HideWindow/CmdLine and windowsShellCommandLine
are guarded by //go:build windows; runner_test.go drops the winproc import.
Linux cross-compile and Windows test run both clean.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Calls desk.SetSystemTrayWindow(w) after SetSystemTrayMenu so a left-click
on the tray icon shows/focuses the window without opening the menu. The
explicit "Show" menu item is kept for right-click access.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Give the window titlebar, taskbar, and tray a size-appropriate icon with a
transparent boundary on both Windows and Linux, instead of scaling one PNG to
every size.
Assets:
- Regenerate the PNGs and gosentry.ico with feathered color-to-alpha so the
rounded-tile boundary is transparent; a binary white-key had left an opaque
halo that read as a border on dark taskbars/trays.
- Rebuild gosentry.ico as multi-size (16 hand-tuned + 32/48/256 from big) and
add a single-frame 16x16 gosentry-icon-16x16.ico for the Windows tray.
- assets.go: add IconSmall() and IconSmallICO().
Wiring:
- Windows window/taskbar: embed gosentry.ico under the GLFW_ICON resource and
skip a.SetIcon so GLFW selects the right frame per size (hand-tuned 16 for the
titlebar, a larger frame for the taskbar).
- Windows tray: SetSystemTrayIcon(IconSmallICO()), a 16x16 ICO frame.
- Linux window titlebar: a.SetIcon(IconSmall()) for a crisp ~16px _NET_WM_ICON;
tray uses the big PNG since StatusNotifierItem renders larger.
Also bump Fyne 2.6.3 -> 2.7.4 (systray 1.11.0 -> 1.12.1) and document the full
cross-platform icon strategy in assets.go, gosentry.rc, run.go, and tray.go.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add chooseFile helper (dialog.NewFileOpen) in settings_view.go and wrap
the Command entry in job_dialog.go with a Browse button so users can
pick an executable from a file picker instead of typing the path.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add Service.ShouldNotifyOnFailure() (reads config under mu) and call
fyne.CurrentApp().SendNotification in the mainwindow subscriber when a
Manual/Schedule run completes with State == "Failed" and the setting is on.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Phase 3 complete: execution modes and overlap policies for job dispatch.
- Bumped version from 0.6.0 to 0.7.0 in src/app/version.go
- Added 0.7.0 entry to docs/CHANGELOG.md documenting parallel/sequential
execution modes and skip/queue overlap policies, and their interaction
with the Settings UI and manual runs.
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
New src/app/run_test.go exercises the execution mode + overlap policy
dispatch paths added in P3.3, reusing the fake runJob seam:
- parallel mode starts all due jobs concurrently
- sequential mode serializes due jobs one at a time
- skip overlap policy drops a re-fire while running (no Pending)
- queue overlap policy marks Pending and re-runs after the run finishes
- RunNow refuses a manual run while another job runs in sequential mode
Helpers prime NextDue to the past and tick at the wall clock so a started
job (advanced an hour out) does not spuriously re-fire.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add Execution mode and Overlap policy widget.Select controls in
ui/settings_view.go under a new Queue section group, between the
Application and Storage sections. Both selects are primed from the
current config on open and their selected values are written back into
the config struct on save, passing through the existing UpdateSettings
validation path.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Rework RunDue/startRunLocked/executeRun so the ExecutionMode and
OverlapPolicy config knobs take effect:
- startRunLocked advances NextDue to the next occurrence instead of
zeroing it, keeping the schedule marching during an in-flight run.
- RunDue scans all due jobs: parallel starts every due, non-running job;
sequential defers a due job while any other job runs. When a job comes
due again mid-run, skip drops it and queue marks it Pending; either way
NextDue is advanced past the fired occurrence.
- executeRun re-runs a Pending job once the current run finishes.
- RunNow gains a sequential-mode guard refusing a manual run while
another job is running.
- Add anyRunningLocked and advanceNextDueLocked helpers.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Extracts the four dispatch functions and their private helper
runningOutput into a dedicated file so operations.go stays focused on
CRUD and settings. No behaviour change; shared helpers (prependLog,
refreshNextRunLocked, etc.) remain in operations.go where other
operations already call them.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Introduces ExecutionMode (parallel/sequential) and OverlapPolicy
(skip/queue) types and constants in domain/config.go, wires defaults
(parallel/skip) into loadOrCreateConfig and the normalization pass, and
adds validation in validateConfig. Adds Pending bool to JobRuntime as
the flag P3.3 will use to re-run a queued overlap. Marks P3.1 done.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Phase 2 (PySentry legacy removal) complete: removed legacy autostart code
for Windows and Linux, updated ignore files for JSON storage.
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Drop cleanupLegacySystemdAutostart, cleanupLegacyDesktopAutostart,
legacySystemdAutostartExists, legacyDesktopAutostartExists, and their
path helpers; remove calls from SetAutostart and AutostartStatus; drop
the os/exec import. Remove TestLinuxAutostartRemovesLegacyDesktopEntry
which referenced the deleted legacyAutostartDesktopPath function.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Drop legacyAutostartName, cleanupLegacyRegistryAutostart,
legacyRegistryAutostartExists, parseRegistryRunValue, and readShortcutTarget
from autostart_windows.go; remove the corresponding legacy registry checks
from SetAutostart and AutostartStatus; delete TestParseRegistryRunValue.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Phase 1 complete: JSON storage + exit-code removal (P1.1-P1.6).
Settings and jobs now persist as gosentry.json / jobs.json with
one-time YAML import for existing installs.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Remove the SuccessExitCodes field from domain.Job and every layer that
read or wrote it: runner/exitcodes.go (deleted), runner.go runStateDetail
simplified to 0=OK / non-zero=Failed, logfile.go, format.go, operations.go,
store.go, job_dialog.go, and jobs_view.go. Tests updated accordingly.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
When gosentry.json / jobs.json are absent, read a pre-migration
gosentry.yaml / jobs.yaml via private yaml-tagged shadow structs and
return the data unsaved; the existing SaveConfig/SaveJobs in OpenStore
then rewrite it as JSON. Replaces the placeholder that pointed configPath
at the legacy YAML file and tried to json.Unmarshal it.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- ConfigFileName → "gosentry.json", JobsFileName → "jobs.json"
- Remove exported LegacyConfigFileName ("pysentry.yaml")
- Add unexported legacyYAMLConfigFileName / legacyYAMLJobsFileName for
the upcoming one-time YAML import (P1.4)
- Update store.go fallback path and comments to describe YAML→JSON
migration rather than the old PySentry→GoSentry rename
- Align store_test.go references and test comments
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace writeYAML with writeJSON (json.MarshalIndent, 2-space indent,
trailing newline) and switch the config and jobs Unmarshal calls to
encoding/json. Mark P1.2 done in the pre-release task list.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Replace yaml:"…" tags with json:"…" on Job, Config, and JobsFile.
Add omitempty to bool fields in Config that previously lacked it.
Update comments to reference .json filenames.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
## Summary
Completed Phase 5 refactoring and reached the target architecture.
**Architectural milestone achieved:**
- Service layer owns all state and is the sole writer
- UI is a thin Fyne view, all widget updates marshaled via `fyne.Do`
- Core engines are stateless and injectable
- Domain types are pure (no `yaml:"-"` fields)
- Full module builds and `go vet ./...` clean
## Changes
- Bump version: 0.3.6 → 0.4.0
- Update CHANGELOG with Phase 5 summary
- Add ROADMAP "Refactoring Follow-Ups" section
## Known follow-up work
1. **Linux test build broken** — `runner_test.go` needs `//go:build windows` tag
2. **File-size limits exceeded** — `operations.go` (486 lines), `jobs_view.go` (415 lines)
See ROADMAP.md for details.
---------
Co-authored-by: mixeme <mix.public@ya.ru>
Reviewed-on: #1
readShortcut read the shortcut TargetPath via [Console]::Out.Write, which
uses the system OEM code page by default. On Russian Windows (CP866) this
encoded Cyrillic characters differently from UTF-8, so Go's string(output)
produced a garbled path that never matched os.Executable, causing
AutostartStatus to always report "shortcut points to another executable"
for any install directory that contained non-ASCII characters.
Fix: prepend [Console]::OutputEncoding = New-Object System.Text.UTF8Encoding($false)
to the readShortcut PowerShell script so the output is always UTF-8.
Also harden sameWindowsPath against NTFS 8.3 short names: when a directory
name contains spaces Windows assigns a short name (e.g. LOCALG~1 for
"Local Git"), and the OS may use that form when launching from a
Startup-folder shortcut. Add an os.SameFile fallback that compares paths
by volume serial number and file index, which is immune to 8.3 vs long
name differences as well as directory junction points.
Add normalizeWindowsPath helper that strips quotes and the \?\ extended-
length prefix before filepath.Clean so those variants compare equal to
the plain path.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>