Commit Graph

79 Commits

Author SHA1 Message Date
mixeme 91679efa3c T4.3: Extract job_dialog.go; validate schedule via domain.Validate
Moves showJobDialog out of jobs_view.go into its own file and adds a
domain.Validate call on the schedule field so malformed cron expressions
are rejected with an error dialog before onSave is invoked.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-21 23:19:00 +03:00
mixeme 760700f6ac T4.2: Extract jobs_view.go (list + details + toolbar)
Move the jobs list, details panel, folder filter, toolbar buttons, and
all related helpers out of mainwindow.go into the new jobs_view.go.
newMainView now calls newJobsView(w, svc) which returns a panel and a
refresh closure; the subscriber and history wiring remain in mainwindow.go.

showJobDialog lives in jobs_view.go as a temporary home until T4.3
extracts it to job_dialog.go.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-21 23:07:34 +03:00
mixeme f28df5e77b docs: plan tray click via Fyne 2.7 SetSystemTrayWindow
Fyne 2.7.0 adds desktop.App.SetSystemTrayWindow, which shows the window
on left-click and unblocks the deferred Tray Interaction item. Record the
2.6.3 -> 2.7.x upgrade dependency and the concrete wiring in run.go.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-19 21:09:55 +03:00
mixeme f82eca8777 T4.1: Rename gui->ui, split lifecycle into run.go + mainwindow.go
Carve src/gui/app.go into the new src/ui package:
- run.go: process lifecycle (single instance, app/window, tray, startup
  timing).
- mainwindow.go: view assembly + the app.Service event listener.

Route every widget update driven by Service events through fyne.Do so the
run goroutine (executeRun) no longer mutates Fyne widgets directly. Also
wrap serveSingleInstance's Show/RequestFocus, which runs on the Accept
goroutine. (Resolves refactoring problem #4.)

fyne.Do/DoAndWait only exist in Fyne v2.6+, so upgrade fyne.io/fyne/v2
v2.5.3 -> v2.6.3. Mark the tray Quit item IsQuit so Fyne's
addMissingQuitForMenu reuses it instead of appending a second, localized
Quit now that v2.6 ships Russian translations.

go build / go vet / go test -race all clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-19 20:55:23 +03:00
mixeme c5e0ef9617 Bump version to 0.3.5: Phase 3 service and state management refactoring complete
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-06-19 08:36:12 +03:00
mixeme 6754d0405b Merge branch 'docs/refactoring-plan' of https://gitea.mixdep.ru/mix/gosentry into docs/refactoring-plan 2026-06-19 08:34:08 +03:00
mixeme 6073d5e112 T3.6: Add src/app unit tests (no Fyne)
Fill the test gaps in the app package, raising coverage from 82% to 97%:

- format_test.go: cover all display helpers moved in T3.5 (StatusText,
  EventText, Display*), previously untested.
- operations_test.go: add RunNow not-found and refused-while-running
  paths, DeleteJob/SetEnabled not-found errors, UpdateJob re-enable,
  runtimeForLocked lazy-recreate, UpdateSettings invalid-config branches,
  and prependLog activity-list capping.

All tests are Fyne-free, using a temp-dir store, a fake runner seam, and
a fake Clock. go vet and go test -race pass across the module.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-19 08:31:04 +03:00
mixeme cf2af1d464 @
T3.6: Add src/app unit tests (no Fyne)

Fill the test gaps in the app package, raising coverage from 82% to 97%:

- format_test.go: cover all display helpers moved in T3.5 (StatusText,
  EventText, Display*), previously untested.
- operations_test.go: add RunNow not-found and refused-while-running
  paths, DeleteJob/SetEnabled not-found errors, UpdateJob re-enable,
  runtimeForLocked lazy-recreate, UpdateSettings invalid-config branches,
  and prependLog activity-list capping.

All tests are Fyne-free, using a temp-dir store, a fake runner seam, and
a fake Clock. go vet and go test -race pass across the module.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@
2026-06-19 08:30:42 +03:00
mixeme 6e5c42fbed T3.5: Move display helpers to src/app/format.go
Extract pure display/formatting functions from the GUI:
- StatusText, EventText, DisplayFolder, DisplayArguments,
  DisplaySuccessExitCodes, DisplayRunMode, DisplayInvocation, DisplayIndex.

These have no Fyne dependencies and serve as a shared formatting layer for
the service to use (e.g. StatusText in the tests when mocking the GUI).
The GUI imports them as app.DisplayFolder(...) etc.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-06-19 08:25:37 +03:00
mixeme a4c93a5122 T3.4: Convert scheduler to drive app.Service; inject Clock
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>
2026-06-19 08:22:35 +03:00
mixeme d8ab9acf7e T3.3: Add state-mutating operations to app.Service
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>
2026-06-19 07:48:41 +03:00
mixeme 5e51381b7a T3.2: Add app event types and observer dispatch
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>
2026-06-19 07:39:34 +03:00
mixeme 9931ec1237 T3.1: Add app.Service owning state behind a mutex
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>
2026-06-19 07:34:34 +03:00
mixeme 98f692658a Bump version to 0.3.4: Phase 2 domain cleanup complete
- Split durable Job from transient JobRuntime
- Extracted Schedule value object
- Made RunJob pure (no job mutation)
- Simplified runtime initialization

No observable behavior changes.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-06-19 00:10:41 +03:00
mixeme b1874845d5 T2.3: Split domain.Job (durable) from domain.JobRuntime (transient)
Move all transient execution state off domain.Job into a new
domain.JobRuntime, keyed by job ID:

- domain: Job now holds only durable YAML fields; remove the yaml:"-"
  fields (LastRun/NextRun/LastState/Logs/Output) and NextDue. Add
  runtime.go with JobRuntime plus NewRuntime/NewRuntimes constructors,
  which now own the runtime-init logic moved out of normalizeJobs.
- runner: RunJob no longer mutates the job; it is pure and returns the
  RunRecord for the caller to fold into the runtime.
- scheduler: take a shared map[int]*JobRuntime and route status/next-run
  bookkeeping through runtimeFor(job); prepareNextRun writes a *JobRuntime.
- storage: normalizeJobs touches only durable config.
- gui: own the runtime map (NewRuntimes), share it with the scheduler,
  and read/write runtime state via runtimeFor; maintain the map by ID on
  add/edit/delete.
- tests: update scheduler/storage tests to the split; tidy a pre-existing
  import-order nit in scheduler.go.

This also satisfies T2.4 (storage load/save only Job, runtime init in
domain.NewRuntime, round-trip tests), since removing the fields forced it.
Runtime-map ownership remains GUI-side glue until T3.1.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-19 00:08:05 +03:00
mixeme ca673f08f9 T2.2: Migrate scheduler to use domain.Schedule
Parse each job's schedule once on load (resetNextRuns) and on edit
(RefreshSchedule) via the new parseJobSchedule helper, caching the
result in a map[int]domain.Schedule keyed by job ID. prepareNextRun
now looks up the cached Schedule instead of re-parsing the string on
every call. Remove the nextRunTime wrapper that did the per-call
parsing. Drop the three scheduler_test.go tests that duplicated
coverage already in domain/schedule_test.go.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-18 22:59:46 +03:00
mixeme 0f17782174 T2.1: Add domain.Schedule value object
Introduce src/domain/schedule.go with a Schedule value object that
centralizes schedule parsing and validation: Parse, Validate, and
Next(time.Time). It owns the cron parser and @every handling, moved out
of the scheduler. The scheduler's nextRunTime is kept as a thin wrapper
delegating to domain.Parse for now (T2.2 will parse once on load/edit).

Add unit tests covering invalid specs, @every intervals, five-field cron,
cron descriptors, whitespace trimming, the zero-value Next, and String.

Mark T2.1 complete in REFACTORING.md.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-18 22:55:20 +03:00
mixeme 9938164c7a Update changelog for 0.3.3: Phase 1 refactoring complete 2026-06-18 22:42:42 +03:00
mixeme 80b6892266 Bump version to 0.3.3 2026-06-18 22:41:25 +03:00
mixeme 29c973464c Mark T1.8 complete in REFACTORING.md 2026-06-18 22:40:45 +03:00
mixeme 2f5c920c3e T1.8: Delete empty src/core; move version to src/app
Move version.go from src/core to src/app to prepare for deleting the
now-empty src/core package. Update all imports and build script
references to use src/app.Version instead of src/core.Version.

Fix import aliasing conflict between Fyne's desktop package and our
platform/desktop package by aliasing Fyne imports as fyneapp and
fynedesktop.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-06-18 22:40:34 +03:00
mixeme 17c5e15b63 Mark T1.7 complete in REFACTORING.md 2026-06-18 22:38:03 +03:00
mixeme 794ed8061c T1.7: Create src/platform/desktop; move desktop integration
Move desktop_linux.go and desktop_other.go from src/core to the new
src/platform/desktop package. Update src/gui to import and use the
new package location.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-06-18 22:37:47 +03:00
mixeme 16d818d03d T1.6: Create src/platform/autostart; move autostart logic
Move autostart_*.go and tests from src/core to src/platform/autostart.
Update src/gui/app.go to call autostart.SetAutostart / autostart.AutostartStatus.
Add quoteDesktopExec to desktop_linux.go (was co-located in autostart_linux.go).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-18 22:35:51 +03:00
mixeme 06edbfff77 T1.5: Create src/storage; move store/paths logic
Move store.go, paths.go, and store_test.go from src/core into the new
src/storage package. Update src/scheduler and src/gui to import storage
instead of core for Store/Paths/OpenStore. Empty the moved files in core
to preserve the package declaration for the remaining core symbols.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-18 22:15:40 +03:00
mixeme ad0e45a7dd T1.4: Create src/scheduler; move scheduler logic
Move scheduler.go and scheduler_test.go from src/core to the new
src/scheduler package. The scheduler still takes *[]domain.Job and
*core.Store (storage moves in T1.5). Update src/gui/app.go to import
the new package; rename the local variable to sched to avoid shadowing
the scheduler package name.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-18 22:03:16 +03:00
mixeme c29d581fb1 T1.3: Create src/runner; move runner logic
Move runner.go, runner_windows.go, runner_other.go, runner_test.go from
src/core to src/runner (package runner). Split helpers into focused files:
invocation*.go, exitcodes.go, logfile.go, cleanup.go. Export LogArguments
and SuccessExitCodesText for use by scheduler's runningOutput helper.
Update src/core/scheduler.go and src/gui/app.go to import src/runner.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-18 21:47:29 +03:00
mixeme f4fb16c0ed T1.2: Create src/platform/winproc; move configureHiddenWindow
Extract the hidden-window logic out of src/core runner and autostart files
into a new platform/winproc package with per-OS build-tag files. All call
sites updated to use winproc.ConfigureHiddenWindow. Builds clean on both
Windows and Linux; all tests pass.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-18 21:28:25 +03:00
mixeme 80c76a0cba T1.1: Create src/domain; move Job/RunRecord/Config/JobsFile/StartInTrayArgument
Extracts the five domain types out of src/core/model.go into a new
src/domain package (job.go, record.go, config.go). The unexported
nextDue field is promoted to NextDue so it is accessible from core.
All references across src/core, src/gui, and cmd/gosentry are updated
to use domain.TypeName. src/core/model.go is reduced to a bare package
declaration. Windows and Linux cross-compilation both pass; all tests
remain green.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-18 21:18:57 +03:00
mixeme 462752f995 Mark T0.2 as complete 2026-06-18 20:25:39 +03:00
mixeme ef6902d65c T0.2: Add characterization tests at refactoring seams
Pin current behavior at the three seams that will move during refactoring:

Store (store_test.go):
- TestJobsRoundTrip: all durable Job fields survive a writeYAML→loadOrCreateJobs
  cycle; runtime fields (LastRun, LastState, Logs) do not.
- TestConfigRoundTrip: all Config fields survive a writeYAML→loadOrCreateConfig
  cycle, including non-default booleans and custom dirs.
- TestNormalizeJobsFillsDefaults: blank jobs get default name/schedule/exitcodes
  and the correct LastState/NextRun for enabled vs disabled.

Scheduler (scheduler_test.go):
- TestNextRunTimeRejectsInvalidSchedules: empty, whitespace, bare @every,
  invalid/negative/zero durations, invalid cron, out-of-range minute all return false.
- TestPrepareNextRunSetsDisplayString: valid schedule writes NextRun as
  "YYYY-MM-DD HH:MM:SS" and sets nextDue to the matching time.Time.
- TestPrepareNextRunSetsInvalidScheduleLabel: bad schedule writes "Invalid
  schedule" and zeroes nextDue.

Runner (runner_test.go):
- TestRunJobLogFileAllHeaders: all log header fields are present (job_id,
  job_name, trigger, state, detail, command, arguments, success_exit_codes,
  start_only, stdout, stderr) and time parses as 2006-01-02 15:04:05.
- TestRunJobRecordFields: RunRecord matches the job and trigger; Time parses;
  Output contains stdout/stderr sections.
- TestFormatOutput / TestFormatOutputEmptyStreams: stdout/stderr sections are
  separated by a blank line; empty streams show "<empty>".
- TestLogArguments: empty/whitespace → "<empty>"; CRLF → LF normalised.
- TestSanitizeFileName: special chars → "_"; empty or all-special → "job".

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-18 20:25:27 +03:00
mixeme 520a7ef98b Mark T0.1 as complete 2026-06-18 20:14:06 +03:00
mixeme 0038975adc T0.1: Add test scripts and documentation
Add scripts/test.sh and scripts/test.bat to run go vet and go test -race.
Update docs/TESTS.md with test script usage and reorganized manual test commands.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-06-18 20:13:46 +03:00
mixeme f653b1e484 Add task completion checklist to refactoring plan
Track the 30 tasks across 5 phases with checkboxes. Each checkbox can be
marked complete as tasks land and pass review.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-06-18 08:19:02 +03:00
mixeme 4c49104cce Add refactoring plan document
Document a phased plan to restructure GoSentry into focused packages
under src/ (domain, storage, runner, scheduler, platform, app, ui) with
an application-service layer that owns state, and link it from the README.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-18 08:16:03 +03:00
mixeme d24211cab2 Fix autostart status for paths with non-ASCII chars and spaces
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>
2026-06-18 07:38:18 +03:00
mixeme 0c2c9f1f67 Add comprehensive test suite documentation
Created docs/TESTS.md documenting all 25 tests across 5 test files:
- store_test.go: YAML serialization tests
- scheduler_test.go: Schedule parsing and invocation output tests
- runner_test.go: Command execution, exit codes, and Windows process tests
- autostart_windows_test.go: Windows startup folder shortcut creation tests
- autostart_linux_test.go: Linux XDG Desktop Entry autostart tests

Includes test descriptions, platform requirements, and usage instructions.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-06-18 00:21:33 +03:00
mixeme 89704b0470 Recommend MinGW Mesa variant in VirtualBox/RDP workaround
Update the OpenGL workaround to suggest the mingw release of Mesa instead of
msvc, as it matches the MSYS2 GCC toolchain used to build GoSentry. Both
variants work at runtime, but mingw is the more consistent choice.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-06-18 00:17:20 +03:00
mixeme 975829ed70 Improve command execution modes 2026-06-18 00:13:12 +03:00
mixeme eb6a1907e6 Document tricky design decisions
Add explanatory comments around startup timing, single-instance focus handoff, config migration, and Windows/Linux autostart choices.

The new comments capture why these implementations were chosen, what alternatives were intentionally avoided, and which user-facing problems those tradeoffs solve.
2026-06-17 23:01:05 +03:00
mixeme 2f7bbe4fca Fix startup timing measurement
Bump the version to 0.3.1 and record startup timing after the main window is actually shown.

Keep autostart launches distinct in History by recording a separate tray-start message when the UI intentionally starts hidden.
2026-06-17 22:57:23 +03:00
mixeme 94033e794f Rename project to GoSentry
Rename the application, Go module path, command package, build artifacts, resource script, and embedded icon assets from PySentry/pysentry to GoSentry/gosentry.

Move portable settings to gosentry.yaml while reading legacy pysentry.yaml during the transition, then rewrite settings under the new name.

Update Windows and Linux autostart integration to use GoSentry names while cleaning up legacy PySentry registry, desktop-entry, and systemd artifacts.

Refresh README, architecture notes, roadmap, changelog, and release examples for version 0.3.0.
2026-06-17 07:29:58 +03:00
mixeme d828e34121 Specify release archive 2026-06-16 22:27:04 +03:00
mixeme 872cc82c5c Release version 0.2.5
Bump the application version to 0.2.5 and update documented artifact names.

Document the Windows VirtualBox/RDP OpenGL startup failure and the Mesa software OpenGL workaround.

Record the tray-icon double-click limitation in the roadmap for future Fyne or platform-specific tray work.
2026-06-16 22:08:20 +03:00
mixeme b1fe8bd675 Start autostart launches in tray
Add a shared --start-in-tray argument that lets autostart start the scheduler and tray integration without opening the main window.

Write the argument into Windows Startup shortcuts and Linux XDG Autostart desktop entries, and verify existing autostart entries include it.

Keep manual launches unchanged and let a manual second launch reveal an already-running instance while duplicate autostart launches stay hidden.
2026-06-16 21:52:00 +03:00
mixeme 44f24ab3d8 Use Startup shortcut for Windows autostart
Replace the HKCU Run autostart entry with a per-user Startup folder shortcut. A .lnk stores TargetPath separately, which avoids fragile quoting when the executable path contains spaces.

Remove legacy PySentry and GoSentry Run entries when saving autostart settings, and report shortcut status from the actual shortcut target.

Add Windows tests that create and read a temporary shortcut with spaces in the path so the PowerShell/COM invocation remains covered.
2026-06-16 21:40:48 +03:00
mixeme 0bc9e91d1e Stabilize Jobs details panel width
Wrap dynamic job detail labels so long names, schedules, commands, and status values no longer change the right panel minimum width when the selected job changes.

Point embedded and Windows resource icons at the current asset filenames so tests and Windows builds continue to work after the asset cleanup.
2026-06-16 21:24:03 +03:00
mixeme 079961e735 Delete old icons 2026-06-16 20:32:11 +03:00
mixeme cc294ce718 Rename 2026-06-16 20:31:09 +03:00
mixeme 5f85af27e9 Rename 2026-06-16 20:30:58 +03:00