Compare commits
3 Commits
da8fe10365
...
v1.0.3
| Author | SHA1 | Date | |
|---|---|---|---|
| 6a03ea4a20 | |||
| 4fb9bf6ff3 | |||
| 9b7a0565ba |
@@ -14,8 +14,11 @@ creating, grouping, pausing, running, and monitoring scheduled shell commands.
|
|||||||
|
|
||||||
<table>
|
<table>
|
||||||
<tr>
|
<tr>
|
||||||
<td align="center"><img src="images/screenshot_jobs.PNG" alt="Jobs tab"><br><em>Jobs tab — job list with details panel and run statistics.</em></td>
|
<td align="center"><img src="docs/screenshots/screenshot_jobs.PNG" alt="Jobs tab"><br><em>Jobs tab — job list with details panel and run statistics.</em></td>
|
||||||
<td align="center"><img src="images/screenshot_settings.PNG" alt="Settings tab"><br><em>Settings tab — application, queue, storage, and version info.</em></td>
|
<td align="center"><img src="docs/screenshots/screenshot_history.PNG" alt="History tab"><br><em>History tab — past runs with trigger, state, and log file.</em></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td align="center" colspan="2"><img src="docs/screenshots/screenshot_settings.PNG" alt="Settings tab"><br><em>Settings tab — application, queue, storage, and version info.</em></td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -17,7 +17,7 @@ import (
|
|||||||
// The hard constraint: Fyne's a.SetIcon and SetSystemTrayIcon each take ONE
|
// The hard constraint: Fyne's a.SetIcon and SetSystemTrayIcon each take ONE
|
||||||
// image, which the OS then scales to every size it needs — titlebar (~16px),
|
// image, which the OS then scales to every size it needs — titlebar (~16px),
|
||||||
// taskbar/dock (~32-48px), and tray. Neither source survives that scaling:
|
// taskbar/dock (~32-48px), and tray. Neither source survives that scaling:
|
||||||
// downscaling the 1254px gosentry-icon-big.png to 16px is muddy, and upscaling
|
// downscaling the 1254px gosentry-icon-large.png to 16px is muddy, and upscaling
|
||||||
// the 16px icon to 32px is blurry. The fix is to feed each surface a
|
// the 16px icon to 32px is blurry. The fix is to feed each surface a
|
||||||
// size-appropriate source — which differs per platform because each platform
|
// size-appropriate source — which differs per platform because each platform
|
||||||
// exposes different icon channels.
|
// exposes different icon channels.
|
||||||
|
|||||||
+10
-6
@@ -41,7 +41,7 @@ flowchart LR
|
|||||||
|
|
||||||
user -->|"edits jobs, settings, runs commands"| ui
|
user -->|"edits jobs, settings, runs commands"| ui
|
||||||
ui -->|"CreateJob, UpdateJob, DeleteJob, RunNow, UpdateSettings, AutostartStatus, …"| svc
|
ui -->|"CreateJob, UpdateJob, DeleteJob, RunNow, UpdateSettings, AutostartStatus, …"| svc
|
||||||
svc -->|"SaveJobs, SaveConfig, LoadJobs, LoadConfig"| store
|
svc -->|"OpenStore, PrepareSaveJobs, PrepareSaveConfig, LoadJobsFile"| store
|
||||||
store -->|"read/write"| config
|
store -->|"read/write"| config
|
||||||
store -->|"read/write"| jobs
|
store -->|"read/write"| jobs
|
||||||
|
|
||||||
@@ -119,10 +119,13 @@ example window-maximized detection, which would need per-OS native calls).
|
|||||||
## Main Flows
|
## Main Flows
|
||||||
|
|
||||||
1. Startup:
|
1. Startup:
|
||||||
`cmd/gosentry` calls `ui.Run`, which creates an `app.Service`, opens the
|
`cmd/gosentry` calls `ui.Run`, which owns the process lifecycle: it calls
|
||||||
store, loads `gosentry.json` and `jobs.json`, subscribes the UI to service
|
`app.Open()` to open the store, load `gosentry.json` and `jobs.json`, and
|
||||||
events, builds the main window, and calls `Service.Start` to begin the
|
build the `app.Service`, then hands that Service to `newMainView`
|
||||||
scheduler loop. On every launch the service seeds per-job run-time statistics
|
(`mainwindow.go`), which subscribes the UI to service events and calls
|
||||||
|
`Service.Start` to begin the scheduler loop before assembling the tabs.
|
||||||
|
`Run` shows the window and, on quit, calls `Service.Stop`.
|
||||||
|
On every launch the service seeds per-job run-time statistics
|
||||||
from existing log files so the details panel reflects accumulated history
|
from existing log files so the details panel reflects accumulated history
|
||||||
immediately (see §Statistics below).
|
immediately (see §Statistics below).
|
||||||
|
|
||||||
@@ -239,6 +242,7 @@ applies to them — and so measure launch latency only.
|
|||||||
| `LastDurationMS` | wall-clock time of the most recent run (launch latency for `StartOnly`) |
|
| `LastDurationMS` | wall-clock time of the most recent run (launch latency for `StartOnly`) |
|
||||||
| `AvgDurationMS` | mean over all runs with a recorded duration, computed as `DurationSumMS / TimedRunCount` on every update rather than folded incrementally, so it never disagrees with the exact sum/count average `runner.aggregateLogStats` computes when seeding from logs |
|
| `AvgDurationMS` | mean over all runs with a recorded duration, computed as `DurationSumMS / TimedRunCount` on every update rather than folded incrementally, so it never disagrees with the exact sum/count average `runner.aggregateLogStats` computes when seeding from logs |
|
||||||
| `MaxDurationMS` | longest recorded run |
|
| `MaxDurationMS` | longest recorded run |
|
||||||
|
| `TimedRunCount` | runs that carried a duration, and so contributed to the aggregates above; a legacy log without a `duration` header counts toward `RunCount` but not this |
|
||||||
| `DurationSumMS` | running total of every timed run's duration; the source `AvgDurationMS` is divided from |
|
| `DurationSumMS` | running total of every timed run's duration; the source `AvgDurationMS` is divided from |
|
||||||
|
|
||||||
`runner.RunJob` measures the wall-clock start→finish and sets `DurationMS` on
|
`runner.RunJob` measures the wall-clock start→finish and sets `DurationMS` on
|
||||||
@@ -271,7 +275,7 @@ the moment the window opens.
|
|||||||
### `jobs_view.go` file structure
|
### `jobs_view.go` file structure
|
||||||
|
|
||||||
The size guideline for a file in this project is ~250 lines.
|
The size guideline for a file in this project is ~250 lines.
|
||||||
`src/ui/jobs_view.go` is split across five files along these seams:
|
`src/ui/jobs_view.go` is split across six files along these seams:
|
||||||
|
|
||||||
| File | Contents |
|
| File | Contents |
|
||||||
|------|----------|
|
|------|----------|
|
||||||
|
|||||||
+122
-39
@@ -2,25 +2,13 @@
|
|||||||
|
|
||||||
All notable GoSentry changes are recorded in this file.
|
All notable GoSentry changes are recorded in this file.
|
||||||
|
|
||||||
## 1.0.2 - 2026-08-05
|
## 1.0.3 - 2026-08-07
|
||||||
|
|
||||||
**KeepRunningInTray is wired to runtime; Windows failure notifications can show
|
**The findings of a whole-project review: durable JSON and log writes, bounded
|
||||||
the app icon (experimental).**
|
History and overlap queues, and a Jobs selection that follows the job.**
|
||||||
|
|
||||||
**Application:**
|
**Application:**
|
||||||
|
|
||||||
- **Keep running in the system tray** now controls behaviour: with the tray on
|
|
||||||
(default), closing the window hides it and autostart uses `--start-in-tray`;
|
|
||||||
with the tray off, closing quits the app and autostart opens the main window.
|
|
||||||
- Saving a tray change updates close behaviour and the autostart entry
|
|
||||||
immediately. The notification-area icon follows the saved value after a
|
|
||||||
restart; Settings shows a hint when a restart is needed (Fyne cannot add or
|
|
||||||
remove the icon mid-session).
|
|
||||||
- A stale autostart shortcut that still passes `--start-in-tray` no longer hides
|
|
||||||
the window when the tray setting is off — saved config wins over the CLI flag.
|
|
||||||
- On Windows, failure toasts can show the app icon: after `NewWindow`,
|
|
||||||
`AppMetadata.Icon` is registered so Fyne picks up artwork without calling
|
|
||||||
`SetIcon`, which would override the PE multi-size window/taskbar icon.
|
|
||||||
- Fixed a Windows quoting bug where a job whose **Command** field held a whole
|
- Fixed a Windows quoting bug where a job whose **Command** field held a whole
|
||||||
command line (a `.bat`/`.cmd` wrapper followed by an argument that itself
|
command line (a `.bat`/`.cmd` wrapper followed by an argument that itself
|
||||||
ended in `.exe`) had its entire command line mistaken for the program path,
|
ended in `.exe`) had its entire command line mistaken for the program path,
|
||||||
@@ -51,17 +39,132 @@ the app icon (experimental).**
|
|||||||
gets slower the longer the app has been running. Measured on 5000 accumulated
|
gets slower the longer the app has been running. Measured on 5000 accumulated
|
||||||
records, one History redraw went from **15.8 ms to 0.9 ms**; at the new cap
|
records, one History redraw went from **15.8 ms to 0.9 ms**; at the new cap
|
||||||
the width rescan alone accounted for 1.5 ms of every redraw.
|
the width rescan alone accounted for 1.5 ms of every redraw.
|
||||||
|
- Two runs of the same job that start within the same second no longer share a
|
||||||
|
log file name. The later one gets a `-2`, `-3`, … suffix instead of silently
|
||||||
|
overwriting the earlier one's log — reachable with a fast manual re-run or a
|
||||||
|
sub-second queue drain.
|
||||||
|
- A hand-edited `jobs.json` in which two entries carry the same `id` no longer
|
||||||
|
leaves them sharing one runtime, one parsed schedule, and one statistics
|
||||||
|
bucket; the duplicate is reassigned a free ID on load, as an absent ID always
|
||||||
|
was.
|
||||||
|
- The **average run duration** shown in Statistics is now the exact sum divided
|
||||||
|
by the timed-run count rather than an incrementally folded integer mean. The
|
||||||
|
old form truncated on every run and the error compounded over a job's life,
|
||||||
|
so the live figure drifted away from the one rebuilt from log files after a
|
||||||
|
restart.
|
||||||
|
- On Linux, a failure to install the `.desktop` file or icon is now reported in
|
||||||
|
History instead of being discarded, so the visible symptom — a generic dock
|
||||||
|
icon — has an explanation.
|
||||||
|
|
||||||
|
**Jobs:**
|
||||||
|
|
||||||
- **The Jobs tab keeps its selection on the job, not on the row.** Selecting a
|
- **The Jobs tab keeps its selection on the job, not on the row.** Selecting a
|
||||||
different jobs file in Settings replaces the whole job list; the details pane
|
different jobs file in Settings replaces the whole job list; the details pane
|
||||||
then described whichever job happened to land on the previously selected row —
|
then described whichever job happened to land on the previously selected row —
|
||||||
or went blank if the new list was shorter — while the highlight in the list
|
or went blank if the new list was shorter — while the highlight in the list
|
||||||
stayed where it was. The selection now follows the job itself, and the
|
stayed where it was. The selection now follows the job itself, and the
|
||||||
highlight and the details pane always describe the same one.
|
highlight and the details pane always describe the same one.
|
||||||
|
- Switching the **Folder** filter now keeps the current selection when the new
|
||||||
|
filter still shows that job, instead of always jumping to the folder's first
|
||||||
|
job.
|
||||||
|
|
||||||
|
**Settings:**
|
||||||
|
|
||||||
- **Max log files and max log age days now accept 0, meaning "keep
|
- **Max log files and max log age days now accept 0, meaning "keep
|
||||||
everything."** Log cleanup already supported disabling either policy; the
|
everything."** Log cleanup already supported disabling either policy; the
|
||||||
Settings form and the Service validator rejected the value that would have
|
Settings form and the Service validator rejected the value that would have
|
||||||
turned it on. A config that already set either to 0 is no longer silently
|
turned it on. A config that already set either to 0 is no longer silently
|
||||||
rewritten back to the 100/30 defaults on load.
|
rewritten back to the 100/30 defaults on load.
|
||||||
|
- Opening the tab and saving no longer block the window while the autostart
|
||||||
|
status is read — on Windows that check shells out to PowerShell, and it now
|
||||||
|
runs off the UI thread.
|
||||||
|
- Two spellings of the same absolute **Jobs file** path (mixed separators, a
|
||||||
|
trailing separator) no longer read as a change of file, so saving no longer
|
||||||
|
triggers a spurious reload of the file already in use.
|
||||||
|
|
||||||
|
**Documentation:**
|
||||||
|
|
||||||
|
- Documentation audited against the code. `ARCHITECTURE.md` — the `jobs_view.go`
|
||||||
|
split is six files, not five (the state extraction was never counted), the
|
||||||
|
statistics table lists `TimedRunCount`, the store edge of the diagram names
|
||||||
|
the methods that exist, and startup says where `Service.Start` is actually
|
||||||
|
called. `TESTS.md` — three tests that had no entry are described
|
||||||
|
(`TestLoadOrCreateConfigPreservesZeroRetentionLimits`,
|
||||||
|
`TestWriteJSONReplacesFileAtomically`,
|
||||||
|
`TestQuoteLeadingWindowsProgramPathPicksEarliestBoundedExtension`), the
|
||||||
|
deliberately-uncovered list covers everything the profile reports at 0%, and
|
||||||
|
the coverage figure records how to read the total rather than the per-package
|
||||||
|
lines. `ROADMAP.md` — the over-the-guideline table was re-measured.
|
||||||
|
- README's scheduler wording caught up with the 0.11.2 rename of "Pause all" to
|
||||||
|
**Disable auto**, and its notification description matches what the app sends.
|
||||||
|
- `STANDARDS.md` records the rules the review settled: no file I/O under
|
||||||
|
`Service.mu`, the History and pending-run caps, the zero-retention meaning,
|
||||||
|
that a start-only process outlives GoSentry, the single-instance fallback's
|
||||||
|
consequence, and the unauthenticated instance-channel port.
|
||||||
|
- `docs/REVIEW.md` (the whole-project review agenda) and the working plan it
|
||||||
|
produced are retired now that every item is either landed here or recorded in
|
||||||
|
`ROADMAP.md`, the way the test review plan was in 1.0.1. `STANDARDS.md` is the
|
||||||
|
surviving reference.
|
||||||
|
- The screenshots moved to `docs/screenshots/`.
|
||||||
|
|
||||||
|
**Internal:**
|
||||||
|
|
||||||
|
- The failure-notification timing diagnostic added in 1.0.2 is now written to
|
||||||
|
`logs/notify-timing.tsv`. The `.tsv` extension keeps it out of `CleanupLogs`,
|
||||||
|
which manages only `.log` files, so it is neither deleted by age nor counted
|
||||||
|
against **Max log files**, and the append now runs off the UI thread.
|
||||||
|
- `jobs.json` is no longer rewritten twice per run. Starting and finishing a run
|
||||||
|
touch only `JobRuntime`, which is never persisted, so both saves re-serialised
|
||||||
|
identical bytes; `SetGlobalPause` did the same alongside its real `SaveConfig`.
|
||||||
|
Removing them also removes the run-start rollback path and the save failure it
|
||||||
|
reported, so `RunDue` no longer has a start error to surface at all.
|
||||||
|
- File I/O no longer happens while `Service.mu` is held — that is the lock the
|
||||||
|
UI thread takes on every job and runtime read, so a JSON write, the
|
||||||
|
post-run log cleanup, or the startup log scan used to make a UI refresh wait
|
||||||
|
on the disk. Saves are now prepared under the lock and written after it is
|
||||||
|
released, in preparation order, so `jobs.json` still ends up matching the
|
||||||
|
in-memory list. Seeding statistics from logs also opens each log file once
|
||||||
|
instead of twice.
|
||||||
|
- The Jobs tab was split into `jobs_view.go` (construction, refresh, layout),
|
||||||
|
`jobs_view_state.go` (the job/runtime snapshot, folder filter, and selection),
|
||||||
|
`jobs_view_list.go`, and `jobs_view_toolbar.go`, joining the existing
|
||||||
|
`jobs_view_details.go` and `jobs_view_helpers.go`. What used to be one
|
||||||
|
330-line constructor whose dozen closures shared seven mutable locals is now
|
||||||
|
widgets reading one named state object — which is what made the selection fix
|
||||||
|
above a change in one place instead of five.
|
||||||
|
- `Service.Store()` is replaced by typed `Service.Config()` and `Service.Paths()`
|
||||||
|
accessors that copy under the lock, so the UI no longer reaches into a shared
|
||||||
|
`*storage.Store`. The Jobs pause control is now driven by `refreshView`
|
||||||
|
reading `svc.Config().Paused` on every event, making it a real consumer of
|
||||||
|
`SchedulerStateChanged`, and the main window's event listener is a type switch.
|
||||||
|
- Dead code removed: `collectActivity`, the `yaml` tags on `RunRecord`, the
|
||||||
|
`logArguments`/`LogArguments` alias, the redundant package-level
|
||||||
|
`SetAutostart`/`AutostartStatus` functions, and the Settings Save handler's
|
||||||
|
second copy of the Service's validation rules. The
|
||||||
|
`systemTrayRegistered`/`mainWindowHidden` globals are one `trayState` value
|
||||||
|
that `Run` owns and threads through.
|
||||||
|
- `scripts/test.bat` no longer prints mojibake for its checkmarks under a
|
||||||
|
non-UTF-8 code page.
|
||||||
|
|
||||||
|
## 1.0.2 - 2026-08-05
|
||||||
|
|
||||||
|
**KeepRunningInTray is wired to runtime; Windows failure notifications can show
|
||||||
|
the app icon (experimental).**
|
||||||
|
|
||||||
|
**Application:**
|
||||||
|
|
||||||
|
- **Keep running in the system tray** now controls behaviour: with the tray on
|
||||||
|
(default), closing the window hides it and autostart uses `--start-in-tray`;
|
||||||
|
with the tray off, closing quits the app and autostart opens the main window.
|
||||||
|
- Saving a tray change updates close behaviour and the autostart entry
|
||||||
|
immediately. The notification-area icon follows the saved value after a
|
||||||
|
restart; Settings shows a hint when a restart is needed (Fyne cannot add or
|
||||||
|
remove the icon mid-session).
|
||||||
|
- A stale autostart shortcut that still passes `--start-in-tray` no longer hides
|
||||||
|
the window when the tray setting is off — saved config wins over the CLI flag.
|
||||||
|
- On Windows, failure toasts can show the app icon: after `NewWindow`,
|
||||||
|
`AppMetadata.Icon` is registered so Fyne picks up artwork without calling
|
||||||
|
`SetIcon`, which would override the PE multi-size window/taskbar icon.
|
||||||
|
|
||||||
**Jobs:**
|
**Jobs:**
|
||||||
|
|
||||||
@@ -77,30 +180,10 @@ the app icon (experimental).**
|
|||||||
|
|
||||||
**Internal:**
|
**Internal:**
|
||||||
|
|
||||||
- App-side failure-notification timing is appended to `logs/notify-timing.tsv`
|
- App-side failure-notification timing is appended to `logs/notify-timing.log`
|
||||||
for diagnosing toast delay (OS latency excluded). The `.tsv` extension keeps
|
for diagnosing toast delay (OS latency excluded).
|
||||||
the diagnostic file out of `CleanupLogs`, which manages only `.log` files, so
|
`scripts/measure-windows-toast.ps1` measures the PowerShell baseline on
|
||||||
it is neither deleted by age nor counted against **Max log files**. The append
|
Windows.
|
||||||
runs off the UI thread. `scripts/measure-windows-toast.ps1` measures the
|
|
||||||
PowerShell baseline on Windows.
|
|
||||||
- `jobs.json` is no longer rewritten twice per run. Starting and finishing a run
|
|
||||||
touch only `JobRuntime`, which is never persisted, so both saves re-serialised
|
|
||||||
identical bytes; `SetGlobalPause` did the same alongside its real `SaveConfig`.
|
|
||||||
Removing them also removes the run-start rollback path and the save failure it
|
|
||||||
reported, so `RunDue` no longer has a start error to surface at all.
|
|
||||||
- File I/O no longer happens while `Service.mu` is held — that is the lock the
|
|
||||||
UI thread takes on every job and runtime read, so a JSON write, the
|
|
||||||
post-run log cleanup, or the startup log scan used to make a UI refresh wait
|
|
||||||
on the disk. Saves are now prepared under the lock and written after it is
|
|
||||||
released, in preparation order, so `jobs.json` still ends up matching the
|
|
||||||
in-memory list. Seeding statistics from logs also opens each log file once
|
|
||||||
instead of twice.
|
|
||||||
- The Jobs tab was split into `jobs_view.go` (construction, refresh, layout),
|
|
||||||
`jobs_view_state.go` (the job/runtime snapshot, folder filter, and selection),
|
|
||||||
`jobs_view_list.go`, and `jobs_view_toolbar.go`. What used to be one 330-line
|
|
||||||
constructor whose dozen closures shared seven mutable locals is now widgets
|
|
||||||
reading one named state object — which is what made the selection fix above a
|
|
||||||
change in one place instead of five.
|
|
||||||
|
|
||||||
## 1.0.1 - 2026-08-04
|
## 1.0.1 - 2026-08-04
|
||||||
|
|
||||||
|
|||||||
+3
-2
@@ -280,8 +280,9 @@ Before tagging:
|
|||||||
|
|
||||||
1. Bump `src/app/version.go`. The tag must match it exactly.
|
1. Bump `src/app/version.go`. The tag must match it exactly.
|
||||||
2. Add the version's [CHANGELOG.md](CHANGELOG.md) section.
|
2. Add the version's [CHANGELOG.md](CHANGELOG.md) section.
|
||||||
3. Retake the README screenshots (`images/screenshot_jobs.PNG`,
|
3. Retake the README screenshots (`docs/screenshots/screenshot_jobs.PNG`,
|
||||||
`images/screenshot_settings.PNG`) if the GUI changed its appearance. This is
|
`docs/screenshots/screenshot_settings.PNG`,
|
||||||
|
`docs/screenshots/screenshot_history.PNG`) if the GUI changed its appearance. This is
|
||||||
easy to forget because nothing fails without it: `README.md` is packaged
|
easy to forget because nothing fails without it: `README.md` is packaged
|
||||||
inside every release archive and is what the forge shows on the project page,
|
inside every release archive and is what the forge shows on the project page,
|
||||||
so a stale shot advertises an application that no longer exists. Take them
|
so a stale shot advertises an application that no longer exists. Take them
|
||||||
|
|||||||
+16
-13
@@ -143,24 +143,25 @@ Design notes / open questions:
|
|||||||
|
|
||||||
[ARCHITECTURE.md](ARCHITECTURE.md) sets a ~250-line guideline per source file
|
[ARCHITECTURE.md](ARCHITECTURE.md) sets a ~250-line guideline per source file
|
||||||
and records the `jobs_view.go` and `settings_view.go` splits as the worked
|
and records the `jobs_view.go` and `settings_view.go` splits as the worked
|
||||||
examples. `jobs_view.go` was split again in 1.0.2 — into view, state, list, and
|
examples. `jobs_view.go` was split again in 1.0.3 — into view, state, list, and
|
||||||
toolbar — because the selection defect it carried was a symptom of the size
|
toolbar — because the selection defect it carried was a symptom of the size
|
||||||
(one 330-line constructor over seven shared locals). Five non-test files are
|
(one 330-line constructor over seven shared locals). Six non-test files are
|
||||||
over the guideline as of that pass:
|
over the guideline:
|
||||||
|
|
||||||
| File | Lines |
|
| File | Lines |
|
||||||
|------|-------|
|
|------|-------|
|
||||||
| `src/app/operations.go` | 529 |
|
| `src/app/operations.go` | 529 |
|
||||||
| `src/ui/history_view.go` | 373 |
|
| `src/storage/store.go` | 382 |
|
||||||
| `src/storage/store.go` | 365 |
|
| `src/ui/history_view.go` | 355 |
|
||||||
| `src/ui/settings_view.go` | 318 |
|
| `src/ui/settings_view.go` | 326 |
|
||||||
| `src/app/run.go` | 274 |
|
| `src/app/run.go` | 275 |
|
||||||
|
| `src/app/service.go` | 252 |
|
||||||
|
|
||||||
The remaining five are deliberately deferred rather than done piecemeal: a
|
The remaining six are deliberately deferred rather than done piecemeal: a
|
||||||
split touches every reader of the file, and doing them in one pass keeps the
|
split touches every reader of the file, and doing them in one pass keeps the
|
||||||
seams consistent instead of settling them five different ways. Splitting is
|
seams consistent instead of settling each one its own way. Splitting is
|
||||||
also the kind of change that reads as pure movement while quietly dropping a
|
also the kind of change that reads as pure movement while quietly dropping a
|
||||||
function, so it wants one careful pass, not five hurried ones.
|
function, so it wants one careful pass, not a hurried one per file.
|
||||||
|
|
||||||
Seams visible today, as a starting point rather than a decision:
|
Seams visible today, as a starting point rather than a decision:
|
||||||
|
|
||||||
@@ -174,9 +175,11 @@ Seams visible today, as a starting point rather than a decision:
|
|||||||
table they size.
|
table they size.
|
||||||
- **`store.go`** — path resolution, the config load/normalize path, and the jobs
|
- **`store.go`** — path resolution, the config load/normalize path, and the jobs
|
||||||
load/normalize path are three separate concerns in one file.
|
load/normalize path are three separate concerns in one file.
|
||||||
- **`run.go`**, **`settings_view.go`** — barely over. Worth re-measuring at the
|
- **`run.go`**, **`settings_view.go`**, **`service.go`** — barely over. Worth
|
||||||
time; if a pass elsewhere has shrunk them, leave them alone rather than
|
re-measuring at the time; if a pass elsewhere has shrunk them, leave them
|
||||||
splitting for the sake of the number.
|
alone rather than splitting for the sake of the number. The counts above move
|
||||||
|
a few lines either way with any edit, so re-measure before acting on them
|
||||||
|
rather than treating the table as current.
|
||||||
|
|
||||||
The `jobs_view.go` pass is the worked example for the rest: the constructor was
|
The `jobs_view.go` pass is the worked example for the rest: the constructor was
|
||||||
broken up along the state it shared, not along line count, and the split landed
|
broken up along the state it shared, not along line count, and the split landed
|
||||||
|
|||||||
+29
-9
@@ -66,20 +66,31 @@ exercised from another one's tests — `domain.NewRuntime`, for instance, is
|
|||||||
covered by the `app` tests. Measure the engine packages together instead:
|
covered by the `app` tests. Measure the engine packages together instead:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
go test -coverpkg=./src/domain,./src/storage,./src/runner,./src/scheduler,./src/app ./src/domain ./src/storage ./src/runner ./src/scheduler ./src/app
|
go test -coverprofile=cover.out -coverpkg=./src/domain,./src/storage,./src/runner,./src/scheduler,./src/app ./src/domain ./src/storage ./src/runner ./src/scheduler ./src/app
|
||||||
```
|
```
|
||||||
|
|
||||||
In the PowerShell environment DEVELOPMENT.md prescribes on Windows, PowerShell
|
In the PowerShell environment DEVELOPMENT.md prescribes on Windows, PowerShell
|
||||||
splits the comma-separated `-coverpkg` list on its own and the command fails
|
splits the comma-separated `-coverpkg` list on its own and the command fails
|
||||||
with `directory not found`. Use the stop-parsing token, or quote the whole
|
with `directory not found`. Use the stop-parsing token, or quote the whole
|
||||||
flag:
|
flag — and note that `--%` swallows the rest of the line, so the profile has to
|
||||||
|
be read by a second command:
|
||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
go test --% -coverpkg=./src/domain,./src/storage,./src/runner,./src/scheduler,./src/app ./src/domain ./src/storage ./src/runner ./src/scheduler ./src/app
|
go test --% -coverprofile=cover.out -coverpkg=./src/domain,./src/storage,./src/runner,./src/scheduler,./src/app ./src/domain ./src/storage ./src/runner ./src/scheduler ./src/app
|
||||||
```
|
```
|
||||||
|
|
||||||
That figure was 84.4% at the 2026-08-04 review, which is the number to compare
|
The total is the last line of the profile summary. It is **not** any of the
|
||||||
against before concluding that coverage has slipped.
|
per-package lines `go test` prints: with `-coverpkg` spanning five packages,
|
||||||
|
each of those reports only what that one package's tests reached across the
|
||||||
|
whole set, so all five are far below the real figure.
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
go tool cover -func=cover.out | Select-Object -Last 1
|
||||||
|
```
|
||||||
|
|
||||||
|
That total was 84.4% at the 2026-08-04 review and 84.1% at the 2026-08-07
|
||||||
|
documentation audit — the number to compare against before concluding that
|
||||||
|
coverage has slipped.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -238,8 +249,8 @@ Tests display-formatting helpers used by the UI.
|
|||||||
| `TestStatusText` | Verifies that job status codes map to the correct display strings. |
|
| `TestStatusText` | Verifies that job status codes map to the correct display strings. |
|
||||||
| `TestEventText` | Verifies trigger-type labels for scheduled, manual, and UI triggers. |
|
| `TestEventText` | Verifies trigger-type labels for scheduled, manual, and UI triggers. |
|
||||||
| `TestEventLine` | Verifies the one-line activity rendering of a `RunRecord`, including the log basename and the `Unknown` fallback for a blank trigger. |
|
| `TestEventLine` | Verifies the one-line activity rendering of a `RunRecord`, including the log basename and the `Unknown` fallback for a blank trigger. |
|
||||||
| `TestDisplayFolder` | Verifies that an empty folder string shows "No folder". |
|
| `TestDisplayFolder` | Verifies that an empty folder string shows "(No folder)". |
|
||||||
| `TestDisplayArguments` | Verifies that an empty arguments string shows "None". |
|
| `TestDisplayArguments` | Verifies that an empty arguments string shows "(none)". |
|
||||||
| `TestDisplayRunMode` | Verifies run-mode labels for normal and start-only modes. |
|
| `TestDisplayRunMode` | Verifies run-mode labels for normal and start-only modes. |
|
||||||
| `TestDisplayInvocation` | Verifies that the full invocation display string combines command and arguments with spacing. |
|
| `TestDisplayInvocation` | Verifies that the full invocation display string combines command and arguments with spacing. |
|
||||||
| `TestDisplayIndex` | Verifies the list position of a job index in a filtered index slice. |
|
| `TestDisplayIndex` | Verifies the list position of a job index in a filtered index slice. |
|
||||||
@@ -265,12 +276,14 @@ Tests JSON round-tripping, default generation, and backward compatibility.
|
|||||||
| `TestLoadOrCreateConfigCreatesDefaultsOnFirstRun` | Verifies that a missing config file is created with sane defaults. |
|
| `TestLoadOrCreateConfigCreatesDefaultsOnFirstRun` | Verifies that a missing config file is created with sane defaults. |
|
||||||
| `TestLoadOrCreateJobsSeedsSampleJobsOnFirstRun` | Verifies that a missing jobs file is created with the sample jobs from `defaultJobs`. |
|
| `TestLoadOrCreateJobsSeedsSampleJobsOnFirstRun` | Verifies that a missing jobs file is created with the sample jobs from `defaultJobs`. |
|
||||||
| `TestLoadOrCreateConfigKeepsZeroTimeoutOnReload` | Verifies that `default_timeout_seconds: 0` survives a reload rather than being normalized away — 0 is a value, not a missing field. |
|
| `TestLoadOrCreateConfigKeepsZeroTimeoutOnReload` | Verifies that `default_timeout_seconds: 0` survives a reload rather than being normalized away — 0 is a value, not a missing field. |
|
||||||
|
| `TestLoadOrCreateConfigPreservesZeroRetentionLimits` | Verifies that `max_log_files` / `max_log_age_days` of 0 read back as 0 ("keep everything") instead of being backfilled to the 100 / 30 defaults — a field the file sets is not the missing-field case. |
|
||||||
| `TestLoadOrCreateConfigMigratesJobsDir` | Verifies that a pre-0.15 `jobs_dir` becomes `jobs_file` pointing at the same `jobs.json`, and that the retired key is not written back. |
|
| `TestLoadOrCreateConfigMigratesJobsDir` | Verifies that a pre-0.15 `jobs_dir` becomes `jobs_file` pointing at the same `jobs.json`, and that the retired key is not written back. |
|
||||||
| `TestLoadOrCreateConfigMigratesLegacyThemeDefault` | Verifies that a config storing the retired `"default"` theme value is normalized to `system` on load. |
|
| `TestLoadOrCreateConfigMigratesLegacyThemeDefault` | Verifies that a config storing the retired `"default"` theme value is normalized to `system` on load. |
|
||||||
| `TestLoadJobsFileReportsMissingWithoutCreating` | Verifies that `LoadJobsFile` reports a missing file as not-found without creating or seeding it, and normalizes the jobs it does load. |
|
| `TestLoadJobsFileReportsMissingWithoutCreating` | Verifies that `LoadJobsFile` reports a missing file as not-found without creating or seeding it, and normalizes the jobs it does load. |
|
||||||
| `TestApplyConfigPathsDerivesJobsDir` | Verifies that the configured jobs file resolves against the program folder and that `Paths.JobsDir` is derived from it. |
|
| `TestApplyConfigPathsDerivesJobsDir` | Verifies that the configured jobs file resolves against the program folder and that `Paths.JobsDir` is derived from it. |
|
||||||
| `TestJobTimeoutRoundTripsThreeStates` | Verifies the on-disk encoding that keeps "inherit" and "no timeout" distinguishable: `nil` is omitted entirely, an explicit `0` is written and read back as set. |
|
| `TestJobTimeoutRoundTripsThreeStates` | Verifies the on-disk encoding that keeps "inherit" and "no timeout" distinguishable: `nil` is omitted entirely, an explicit `0` is written and read back as set. |
|
||||||
| `TestJobsJSONDoesNotPersistRuntimeNoise` | Verifies that `jobs.json` does not persist runtime state (LastRun, NextRun, etc.). Only durable job fields are stored. |
|
| `TestJobsJSONDoesNotPersistRuntimeNoise` | Verifies that `jobs.json` does not persist runtime state (LastRun, NextRun, etc.). Only durable job fields are stored. |
|
||||||
|
| `TestWriteJSONReplacesFileAtomically` | Pins the durability fix: `writeJSON` replaces the destination through a temp file and a rename rather than truncating it in place, and leaves no temp file behind. |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -355,6 +368,7 @@ Tests the Windows shell invocation and hidden-window flags.
|
|||||||
| `TestShellCommandHidesWindow` | Verifies that shell commands request hidden-window startup to prevent console flash. |
|
| `TestShellCommandHidesWindow` | Verifies that shell commands request hidden-window startup to prevent console flash. |
|
||||||
| `TestShellCommandUsesWindowsSafeQuoting` | Verifies `cmd.exe /S /C` quoting for paths with spaces and special characters. |
|
| `TestShellCommandUsesWindowsSafeQuoting` | Verifies `cmd.exe /S /C` quoting for paths with spaces and special characters. |
|
||||||
| `TestWindowsShellCommandLineQuotesUnquotedProgramPath` | Verifies that unquoted program paths in shell commands are quoted while preserving already-quoted arguments. |
|
| `TestWindowsShellCommandLineQuotesUnquotedProgramPath` | Verifies that unquoted program paths in shell commands are quoted while preserving already-quoted arguments. |
|
||||||
|
| `TestQuoteLeadingWindowsProgramPathPicksEarliestBoundedExtension` | Regression: the program path ends at the *earliest* extension match sitting at a token boundary — not the first extension in `.exe`/`.cmd`/`.bat`/`.com` list order, and not a substring inside another word — so a `.bat` wrapper followed by an `.exe` argument still quotes only the wrapper. |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -674,6 +688,12 @@ A coverage run over the non-UI packages reports these as uncovered. All are
|
|||||||
intentional; none is an oversight to be "fixed" with a test.
|
intentional; none is an oversight to be "fixed" with a test.
|
||||||
|
|
||||||
- The real `Clock` — a fake is injected everywhere it is used.
|
- The real `Clock` — a fake is injected everywhere it is used.
|
||||||
- `storage.OpenStore`, `storage.ResolvePaths`, `storage.PeekKeepRunningInTray`, `app.Service.Start`, `app.Service.Open` — process entry points, exercised by running the app.
|
- `storage.OpenStore`, `storage.ResolvePaths`, `storage.PeekKeepRunningInTray`, `app.Service.Start`, `app.Open` — process entry points, exercised by running the app.
|
||||||
- The autostart and desktop-icon wrappers — OS integration, driven only on a real desktop.
|
- The autostart and desktop-icon wrappers (`app.Service.InstallDesktopIcon`, `AutostartStatus`, `ApplyAutostart`) — OS integration, driven only on a real desktop.
|
||||||
- `app.Service.ShouldNotifyOnFailure` — a getter under the mutex.
|
- `app.Service.ShouldNotifyOnFailure` — a getter under the mutex.
|
||||||
|
- `app.Service.Config` and `app.Service.Paths` — read only from `src/ui`, which
|
||||||
|
this run excludes, so they are covered by the suite but not by this profile.
|
||||||
|
The same applies to `storage.Store.SaveJobs`: the engine writes through
|
||||||
|
`PrepareSaveJobs`, and the one-shot wrapper is what `OpenStore` uses.
|
||||||
|
- The five `isEvent` marker methods in `app/events.go` — empty bodies that exist
|
||||||
|
only to close the `Event` interface.
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 37 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 99 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 100 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 65 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 57 KiB |
+1
-1
@@ -3,4 +3,4 @@ package app
|
|||||||
// Version is the application version shown in the GUI and used by build
|
// Version is the application version shown in the GUI and used by build
|
||||||
// scripts in artifact names. It is a var rather than a const so release builds
|
// scripts in artifact names. It is a var rather than a const so release builds
|
||||||
// can override it with Go ldflags when CI tags a build.
|
// can override it with Go ldflags when CI tags a build.
|
||||||
var Version = "1.0.2"
|
var Version = "1.0.3"
|
||||||
|
|||||||
Reference in New Issue
Block a user