From e853727e09f66f76e20864f628fd53b188386856 Mon Sep 17 00:00:00 2001 From: mixeme Date: Mon, 22 Jun 2026 21:04:44 +0300 Subject: [PATCH] Add pre-release milestone plan Document the planned work to take GoSentry from the v0.4.0 architectural milestone to a pre-release build: JSON storage with one-time YAML import, task-queue execution settings (parallel/sequential + overlap policy), failure notifications, Command browse button, small/large icons, the Fyne 2.7 upgrade with tray-click-to-show, dropping the per-job success-exit-codes feature, PySentry legacy cleanup, and a README split. Co-Authored-By: Claude Opus 4.8 --- docs/PRE-RELEASE-PLAN.md | 207 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 207 insertions(+) create mode 100644 docs/PRE-RELEASE-PLAN.md diff --git a/docs/PRE-RELEASE-PLAN.md b/docs/PRE-RELEASE-PLAN.md new file mode 100644 index 0000000..fc8a36b --- /dev/null +++ b/docs/PRE-RELEASE-PLAN.md @@ -0,0 +1,207 @@ +# Pre-Release Milestone Plan + +This document tracks the work that takes GoSentry from the v0.4.0 architectural +milestone to a pre-release-ready build. It closes the remaining +[roadmap](ROADMAP.md) items (except packaging), wires up features that were +stubbed during the refactor, and cleans the tree of legacy/rename scaffolding. + +The goal is a coherent, end-user-ready build: JSON storage, a real task-queue +policy, working failure notifications, no legacy code, proper icons, and an +end-user-focused README. + +Confirmed decisions: + +- **Storage:** switch to JSON. One-time YAML import for `gosentry.yaml` / + `jobs.yaml` (read once, rewrite as JSON). Drop all PySentry legacy entirely. +- **Task queue defaults:** execution mode = Parallel, overlap policy = Skip (both + selectable in Settings). +- **Fyne 2.7 upgrade:** included (enables tray-click-to-show). +- **Per-job success exit codes:** dropped (exit 0 = OK, non-zero = Failed). + +## 1. Switch storage YAML → JSON + +- `src/domain/config.go`, `src/domain/job.go`: replace every `yaml:"..."` struct + tag with `json:"..."` (Config, Job, JobsFile). Keep `omitempty` where used. +- `src/storage/store.go`: replace `writeYAML` with `writeJSON` using + `encoding/json` (`MarshalIndent(value, "", " ")` for human-editable files). + Replace the two `yaml.Unmarshal` calls with `json.Unmarshal`. +- `src/storage/paths.go`: `ConfigFileName = "gosentry.json"`, + `JobsFileName = "jobs.json"`. Remove `LegacyConfigFileName` (pysentry). Add + `legacyYAMLConfigFileName = "gosentry.yaml"` and + `legacyYAMLJobsFileName = "jobs.yaml"` for the import path. +- One-time YAML import in `store.go`: + - `loadOrCreateConfig`: if `gosentry.json` is absent but `gosentry.yaml` + exists, `yaml.Unmarshal` it through a private yaml-tagged shadow struct. + `OpenStore` already calls `SaveConfig` afterward, which writes JSON. + - `loadOrCreateJobs`: same pattern for `jobs.yaml` via a yaml-tagged shadow; + `OpenStore`'s `SaveJobs` rewrites as JSON. + - The old `.yaml` files are left on disk untouched (non-destructive). +- Keep `go.yaml.in/yaml/v4` in `go.mod` (now used only by the import path). +- Tests: `src/storage/store_test.go` — switch the write helper to JSON; replace + the pysentry migration test with one covering the YAML→JSON one-time import. + +## 2. Task-queue execution settings + +Reworks the run-dispatch model so the schedule keeps advancing even while a run +is in flight, which is what makes an overlap policy meaningful. + +- `src/domain/config.go`: add `ExecutionMode` and `OverlapPolicy` (JSON tags) plus + exported constants (`ExecutionParallel`/`ExecutionSequential`, + `OverlapSkip`/`OverlapQueue`). Defaults in `loadOrCreateConfig` and + `validateConfig`: empty → `parallel` / `skip`. +- `src/domain/runtime.go`: add `Pending bool` to `JobRuntime` (a queued overlap). +- Dispatch logic (moved into `src/app/run.go`, see §9): + - `startRunLocked`: instead of zeroing `NextDue`, advance it to the next + occurrence while the display `NextRun` shows `"Running"`, so a due tick can + arrive during a run. + - `RunDue`: scan all due jobs. For each due, enabled job: + - already running → overlap policy: `skip` drops this occurrence; `queue` + sets `runtime.Pending = true`. + - not running → execution mode: `parallel` starts it immediately; + `sequential` starts it only if no job is currently running and none started + earlier in this tick. + - `executeRun`: on finish, if `runtime.Pending` and not paused and the mode + permits, clear `Pending` and start the job again immediately. + - Add `anyRunningLocked()` helper. + - `RunNow`: keep the already-running guard; in sequential mode also refuse if + another job is running. +- `src/ui/settings_view.go`: add a Queue group with `widget.Select` controls for + execution mode and overlap policy, wired into the saved config. +- Tests: extend `src/app/operations_test.go` for parallel/sequential/skip/queue, + reusing the fake-`runJob` seam and `StartWith(fakeClock)`. + +## 3. Browse button for the Command field + +- `src/ui/job_dialog.go`: wrap `commandEntry` in + `container.NewBorder(nil,nil,nil, browseBtn, commandEntry)` (same pattern as the + directory rows in `settings_view.go`) and pass the container as the Command + form item. The button opens a file picker. +- Add a `chooseFile(w, target)` helper in `src/ui/settings_view.go` using + `dialog.NewFileOpen`; on selection set the entry text to `uri.Path()`. + +## 4. System notifications on failure + +`Config.NotifyOnFailure` is stored but never acted on. Wire it to Fyne. + +- Add `func (s *Service) ShouldNotifyOnFailure() bool` (reads config under `mu`). +- `src/ui/mainwindow.go`: in the existing `svc.Subscribe(...)` handler, when the + event is `RunRecorded` with `State == "Failed"`, a real-run trigger + (`Manual`/`Schedule`), and notifications enabled, call + `fyne.CurrentApp().SendNotification(...)`. The handler is already inside + `fyne.Do`. +- Update the Settings checkbox wording to drop the "reserved" note. + +## 5. Application icons — small vs large + +Assets present: `gosentry-icon-16x16.png` (small), `gosentry-icon-big.png` +(large), `gosentry.ico`. + +- `assets/assets.go`: also embed the 16×16; add `IconSmall()`. Keep `Icon()` + (large) for the window/app and `IconBytes()` (large) for Linux desktop + integration. +- `src/ui/run.go`: window/app icon stays large. +- `src/ui/tray.go`: set the tray icon to the small variant via + `desk.SetSystemTrayIcon(assets.IconSmall())` (Fyne 2.7). +- Windows Explorer icon stays via `packaging/windows/gosentry.rc`; confirm + `gosentry.ico` has both a 16×16 and a large frame. + +## 6. Fyne 2.6.3 → 2.7.x upgrade + tray click + +- `go.mod`: bump `fyne.io/fyne/v2` to latest 2.7.x; `go get` + `go mod tidy`. +- Rebuild under MSYS2 UCRT64 (CGO); skim the 2.7 changelog for breaking changes. +- `src/ui/tray.go`: add `desk.SetSystemTrayWindow(w)` so left-click shows/focuses + the window; keep the "Show" menu item. +- Re-measure startup using the existing History "Started … in Xms" event and + append to [PERFORMANCE.md](PERFORMANCE.md). + +## 7. Drop legacy + debug; prepare for pre-release + +- PySentry removal: + - Legacy pysentry config path (done in §1). + - `src/platform/autostart/autostart_windows.go`: remove `legacyAutostartName`, + `cleanupLegacyRegistryAutostart`, `legacyRegistryAutostartExists`, + `parseRegistryRunValue`, and their use in `SetAutostart`/`AutostartStatus`. + - `src/platform/autostart/autostart_linux.go`: remove the legacy systemd + + desktop cleanup functions and their `Set`/`Status` calls. + - Delete the corresponding legacy tests; drop `readShortcutTarget` if unused. + - `.gitignore` / `.dockerignore`: drop the `pysentry.yaml` lines; add + `gosentry.json` / `jobs.json`; keep the `*.yaml` ignores for the import + window. +- Debug/diagnostics: confirm no `GOSENTRY_TIMING` code remains (docs only); keep + the lightweight startup History event (needed for §6). +- Stale artifacts: ensure `dist/` and `cmd/gosentry/*.syso` stay gitignored. +- Bump hardcoded `0.3.0` references to the current `0.4.0`/next pre-release + version in README/docs. + +## 8. README split — end-user vs developer + +- New `docs/DEVELOPMENT.md`: move Requirements (toolchain), Build (all variants), + Run From Source, Project Layout, and Dependencies/mirroring out of README. +- `README.md` keeps end-user content only: intro, Features, Storage, Schedules, + Using the App, Autostart, Troubleshooting (the VirtualBox/RDP OpenGL workaround + stays). Update YAML → JSON file names + examples, the new Queue settings, real + notifications wording, and version strings. Add a Documentation link list to + `docs/`. + +## 9. Roadmap refactoring follow-ups + +- Linux test build fix: move the Windows-only tests + (`TestShellCommandHidesWindow`, `TestShellCommandUsesWindowsSafeQuoting`, and + peers touching `SysProcAttr`/`windowsShellCommandLine`) from + `src/runner/runner_test.go` into a new `src/runner/runner_windows_test.go` + guarded by `//go:build windows`. +- File-size (soft) limits: split the run/dispatch code (`RunDue`, `RunNow`, + `startRunLocked`, `executeRun`, queue helpers) out of + `src/app/operations.go` into a new `src/app/run.go`. Optionally split + `src/ui/jobs_view.go` if a clean seam exists. + +## 10. Drop per-job success-exit-codes feature + +After removal the run outcome is: exit code 0 → `OK`, any non-zero → `Failed`. + +- `src/domain/job.go`: remove the `SuccessExitCodes` field. +- `src/runner/exitcodes.go`: delete the file. +- `src/runner/runner.go` `runStateDetail`: drop the `acceptedExitCode` branch; a + non-zero `exec.ExitError` is always `Failed` with `Exit code %d`. +- `src/runner/logfile.go`: remove the `success_exit_codes` field. +- `src/app/format.go`: remove `DisplaySuccessExitCodes` (and its test). +- `src/app/operations.go`: drop the `success_exit_codes` lines from + `runningOutput` and the default in `normalizeJob`. +- `src/storage/store.go` `normalizeJobs`: drop the default. +- `src/ui/job_dialog.go`: remove the entry, form item, and save. +- `src/ui/jobs_view.go`: remove the label and detail row. +- Tests/docs: remove `TestParseExitCodes`, + `TestRunJobAcceptsConfiguredExitCode`, `TestRunJobRejectsUnconfiguredExitCode`, + the `success_exit_codes` log-content assertions, the store_test exit-code + fields/defaults, and the matching `docs/TESTS.md` rows. +- §1 import: legacy YAML jobs may carry `success_exit_codes`; the shadow struct + ignores it. + +## Implementation order + +1. Storage JSON + one-time import (§1) and exit-code removal (§10) — both touch + `domain/job.go` and `storage/store.go`. +2. PySentry removal (§7 autostart + ignores). +3. Queue model + settings (§2) and the `operations.go` → `run.go` split (§9). +4. Notifications (§4), Command Browse (§3). +5. Icons (§5). +6. Fyne 2.7 upgrade + tray click + startup re-measure (§6). +7. Linux test-build fix (§9). +8. README split + docs/version updates (§8, §7). + +## Verification + +- `go test ./...` must build and pass. Build with CGO under MSYS2 UCRT64 on + Windows (`scripts\test.bat`); the default Bash env has CGO off. Confirm the + Linux test build compiles (`GOOS=linux go vet ./...` where available). +- Manual smoke (Windows GUI): build via `scripts\build-windows.bat` and run. + - First run with an existing `gosentry.yaml`/`jobs.yaml` imports into JSON; a + fresh install creates JSON defaults. + - Job dialog: Browse picks a command path. + - Settings: Queue mode + overlap policy persist; failure notifications toggle; + a failing job (`exit 1`) raises a desktop notification when enabled. + - Queue behavior: a fast-schedule long-running job demonstrates skip vs queue; + parallel runs two due jobs at once; sequential serializes. + - Tray: left-click shows/focuses the window; tray uses the small icon, window + and taskbar use the large icon. +- Record the post-upgrade startup time and append to PERFORMANCE.md.