Compare commits
8 Commits
84e81371c1
..
v1.0.0
| Author | SHA1 | Date | |
|---|---|---|---|
| e482e3261c | |||
| a926e90196 | |||
| 16cf2b9e30 | |||
| 0cf44e1dfa | |||
| 44bc7ee81e | |||
| 0f171edd75 | |||
| 9a45a7be6f | |||
| 9dd461e35e |
@@ -204,9 +204,10 @@ 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. `src/ui/jobs_view.go`
|
The size guideline for a file in this project is ~250 lines.
|
||||||
is split across three files along these seams; the view file itself has grown
|
`src/ui/jobs_view.go` is split across three files along these seams; the view
|
||||||
back over the guideline since, and is the next candidate if it grows further:
|
file itself has grown back over the guideline since — see the split item in
|
||||||
|
[ROADMAP.md](ROADMAP.md), which tracks every file currently over it:
|
||||||
|
|
||||||
| File | Contents |
|
| File | Contents |
|
||||||
|------|----------|
|
|------|----------|
|
||||||
@@ -217,8 +218,7 @@ back over the guideline since, and is the next candidate if it grows further:
|
|||||||
### `settings_view.go` file structure
|
### `settings_view.go` file structure
|
||||||
|
|
||||||
`src/ui/settings_view.go` is split across three files the same way, once its
|
`src/ui/settings_view.go` is split across three files the same way, once its
|
||||||
own size passed the guideline. `src/ui/history_view.go` is over it too and has
|
own size passed the guideline:
|
||||||
not been split:
|
|
||||||
|
|
||||||
| File | Contents |
|
| File | Contents |
|
||||||
|------|----------|
|
|------|----------|
|
||||||
|
|||||||
@@ -46,6 +46,50 @@ dragged.**
|
|||||||
- The **Application** and **About** blocks are about 2 px tighter: every stacked
|
- The **Application** and **About** blocks are about 2 px tighter: every stacked
|
||||||
row group in the app now shares one spacing derived from the theme rather than
|
row group in the app now shares one spacing derived from the theme rather than
|
||||||
three separately tuned numbers.
|
three separately tuned numbers.
|
||||||
|
- The **Theme** dropdown is no longer flush against the **Notifications**
|
||||||
|
checkbox. That shared row spacing pulls rows together by one text inset, which
|
||||||
|
the rows above have to give but a dropdown — which paints its box out to the
|
||||||
|
row's edge — does not, so the gap collapsed to about a pixel. The Theme row
|
||||||
|
now keeps the same gap the checkbox rows have.
|
||||||
|
|
||||||
|
**Documentation:**
|
||||||
|
|
||||||
|
- The **README** describes the application that exists. Its `gosentry.json`
|
||||||
|
sample was three keys short of what the app writes on first run, which made
|
||||||
|
the one file the user is invited to hand-edit the least accurate thing in the
|
||||||
|
document; it is now the real default, with each key explained — including why
|
||||||
|
a zero timeout is written out and an unset one is not. The feature list has
|
||||||
|
caught up with the run timeout, the theme, the compact job list, and the
|
||||||
|
per-job overlap and timeout overrides the job dialog has always offered.
|
||||||
|
- **`docs/DEVELOPMENT.md`** is ordered as stack, external libraries, run from
|
||||||
|
source, build, release, CI, behind a two-level table of contents, instead of
|
||||||
|
opening with MSYS2 setup and burying "Run From Source" mid-document. The
|
||||||
|
library table gains versions and licenses, the `package-*` scripts are
|
||||||
|
documented for the first time and labelled by OS, and the Codeberg
|
||||||
|
`RELEASE_TOKEN` note now states the failure mode rather than leaving it to be
|
||||||
|
inferred from a red job: build and packaging succeed, the upload step fails on
|
||||||
|
authentication and takes the job with it, leaving a published release with no
|
||||||
|
assets. The Project Layout section is gone — it duplicated ARCHITECTURE's
|
||||||
|
package map and had drifted out of date.
|
||||||
|
- **Cutting a GitHub release now documents the push mirror it has to survive.**
|
||||||
|
GitHub is a pruning push mirror of Gitea, so `gh release create` creating the
|
||||||
|
tag itself produces a tag Gitea does not know about, which the next
|
||||||
|
synchronisation deletes — orphaning the release and taking its uploaded
|
||||||
|
archives with it, without a single failed step to point at. The procedure is
|
||||||
|
push the tag to Gitea, wait for the mirror, verify the tag on GitHub, then
|
||||||
|
publish with `--verify-tag`.
|
||||||
|
- **`docs/TESTS.md`** matches the suite it indexes again. It listed 130 tests
|
||||||
|
against 170 in the tree, omitted four test files entirely, and named two tests
|
||||||
|
that no longer exist. Every test function now appears exactly once, under the
|
||||||
|
file it actually lives in.
|
||||||
|
- **`docs/ARCHITECTURE.md`** no longer draws the UI calling the autostart
|
||||||
|
manager directly — it does not, and `src/ui` holds no reference to that
|
||||||
|
package — and `platform/desktop` is described by what it does (the XDG desktop
|
||||||
|
entry and icon) rather than as a display-scale helper.
|
||||||
|
- The **~250-line file guideline** is stated as the target it is, with the six
|
||||||
|
files currently over it recorded as a `docs/ROADMAP.md` item. They are to be
|
||||||
|
split in one pass during the next whole-project review, since six separate
|
||||||
|
passes would settle the same seam question six ways.
|
||||||
|
|
||||||
## 0.15.0 - 2026-07-26
|
## 0.15.0 - 2026-07-26
|
||||||
|
|
||||||
|
|||||||
+206
-81
@@ -1,18 +1,51 @@
|
|||||||
# GoSentry — Development
|
# GoSentry — Development
|
||||||
|
|
||||||
Build instructions, project layout, and dependency information for contributors.
|
Toolchain, dependency, build, and release information for contributors.
|
||||||
|
|
||||||
## Requirements
|
## Contents
|
||||||
|
|
||||||
Common:
|
1. [Technology Stack and Tools](#1-technology-stack-and-tools)
|
||||||
|
- [Toolchain — Windows](#toolchain--windows)
|
||||||
|
- [Toolchain — Linux](#toolchain--linux)
|
||||||
|
- [Repository scripts](#repository-scripts)
|
||||||
|
2. [External Libraries](#2-external-libraries)
|
||||||
|
3. [Run From Source](#3-run-from-source)
|
||||||
|
4. [Building the Executable](#4-building-the-executable)
|
||||||
|
- [Windows](#windows)
|
||||||
|
- [Linux](#linux)
|
||||||
|
- [Linux using Docker](#linux-using-docker)
|
||||||
|
5. [Building a Release](#5-building-a-release)
|
||||||
|
- [All targets from Linux](#all-targets-from-linux)
|
||||||
|
- [Packaging](#packaging)
|
||||||
|
6. [CI](#6-ci)
|
||||||
|
- [Cutting a release](#cutting-a-release)
|
||||||
|
- [Releasing through the GitHub push mirror](#releasing-through-the-github-push-mirror)
|
||||||
|
|
||||||
|
## 1. Technology Stack and Tools
|
||||||
|
|
||||||
|
GoSentry is a single desktop process written in Go with a Fyne GUI. There is no
|
||||||
|
server component and no external runtime: the release artifact is one native
|
||||||
|
executable per platform.
|
||||||
|
|
||||||
|
| Layer | Choice |
|
||||||
|
| --- | --- |
|
||||||
|
| Language | Go 1.22 or newer |
|
||||||
|
| GUI toolkit | Fyne v2 (OpenGL desktop backend) |
|
||||||
|
| Scheduling | `robfig/cron/v3` expression parser |
|
||||||
|
| Persistence | Plain JSON files (`gosentry.json`, `jobs.json`) |
|
||||||
|
| Build | `go build` driven by the scripts in `scripts/` |
|
||||||
|
| Reproducible builds | Docker (`golang:1.22-bookworm` based [Dockerfile](../Dockerfile)) |
|
||||||
|
| CI | GitHub Actions and Forgejo Actions (Codeberg) |
|
||||||
|
|
||||||
|
CGO is mandatory. The Fyne desktop backend links against native OpenGL and
|
||||||
|
window-system libraries, so a C compiler must be present for every build,
|
||||||
|
including `go run` and `go test`.
|
||||||
|
|
||||||
|
### Toolchain — Windows
|
||||||
|
|
||||||
- [Go](https://go.dev/) 1.22 or newer.
|
- [Go](https://go.dev/) 1.22 or newer.
|
||||||
|
- MSYS2 with UCRT64 GCC in `C:\msys64\ucrt64\bin` (plus `windres` for the icon
|
||||||
Windows:
|
resource).
|
||||||
|
|
||||||
- MSYS2 with UCRT64 GCC in `C:\msys64\ucrt64\bin`.
|
|
||||||
|
|
||||||
Install these dependencies on Windows:
|
|
||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
# 1. Install Go 1.22 or newer from https://go.dev/dl/.
|
# 1. Install Go 1.22 or newer from https://go.dev/dl/.
|
||||||
@@ -33,12 +66,12 @@ Test-Path C:\msys64\ucrt64\bin\gcc.exe
|
|||||||
Test-Path C:\msys64\ucrt64\bin\windres.exe
|
Test-Path C:\msys64\ucrt64\bin\windres.exe
|
||||||
```
|
```
|
||||||
|
|
||||||
Linux:
|
### Toolchain — Linux
|
||||||
|
|
||||||
|
- [Go](https://go.dev/) 1.22 or newer.
|
||||||
- A C compiler.
|
- A C compiler.
|
||||||
- [Fyne](https://fyne.io/) native build dependencies, including OpenGL/X11 development packages.
|
- [Fyne](https://fyne.io/) native build dependencies, including OpenGL/X11
|
||||||
|
development packages.
|
||||||
On Debian/Ubuntu, the Linux dependencies are typically:
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Go builds the application, gcc is required by CGO/Fyne, and the OpenGL/X11
|
# Go builds the application, gcc is required by CGO/Fyne, and the OpenGL/X11
|
||||||
@@ -46,7 +79,71 @@ On Debian/Ubuntu, the Linux dependencies are typically:
|
|||||||
sudo apt install golang gcc libgl1-mesa-dev xorg-dev
|
sudo apt install golang gcc libgl1-mesa-dev xorg-dev
|
||||||
```
|
```
|
||||||
|
|
||||||
## Build
|
### Repository scripts
|
||||||
|
|
||||||
|
| Script | Purpose |
|
||||||
|
| --- | --- |
|
||||||
|
| `scripts/test.bat`, `scripts/test.sh` | `go vet ./...` then `go test -race ./...` |
|
||||||
|
| `scripts/build-windows.bat` | Windows amd64 executable |
|
||||||
|
| `scripts/build-linux.sh` | Linux amd64 executable |
|
||||||
|
| `scripts/build-linux-docker.sh` | Linux amd64 executable, built in Docker |
|
||||||
|
| `scripts/build-release-linux.sh` | Multi-target release artifacts from one Linux/Docker workflow |
|
||||||
|
| `scripts/package-windows.bat`, `scripts/package-linux.sh` | Wrap a built binary into a distributable archive |
|
||||||
|
| `scripts/ci-build-release.sh` | Entry point used by both CI workflows |
|
||||||
|
|
||||||
|
Build outputs are written to `dist/`. The package layout is documented in
|
||||||
|
[ARCHITECTURE.md](ARCHITECTURE.md).
|
||||||
|
|
||||||
|
## 2. External Libraries
|
||||||
|
|
||||||
|
GoSentry keeps the direct dependency list intentionally small. GoSentry itself
|
||||||
|
is distributed under the [MIT License](../LICENSE).
|
||||||
|
|
||||||
|
| Dependency | Version | Repository | License |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| Go toolchain | 1.22+ | https://go.googlesource.com/go | BSD 3-Clause |
|
||||||
|
| `fyne.io/fyne/v2` | v2.7.4 | https://github.com/fyne-io/fyne | BSD 3-Clause |
|
||||||
|
| `github.com/robfig/cron/v3` | v3.0.1 | https://github.com/robfig/cron | MIT |
|
||||||
|
|
||||||
|
The remaining entries in `go.mod` are indirect dependencies pulled in by Fyne
|
||||||
|
and the Go module resolver. To list every direct and indirect module used by the
|
||||||
|
current checkout:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
go list -m all
|
||||||
|
```
|
||||||
|
|
||||||
|
## 3. Run From Source
|
||||||
|
|
||||||
|
Windows:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
# Fyne requires CGO on Windows. MSYS2 UCRT64 provides the C compiler and native
|
||||||
|
# libraries used by the desktop backend.
|
||||||
|
$env:Path = 'C:\msys64\ucrt64\bin;' + $env:Path
|
||||||
|
$env:CGO_ENABLED = '1'
|
||||||
|
|
||||||
|
# go run starts the app from source. Use scripts\build-windows.bat when you need
|
||||||
|
# a standalone .exe without a console window.
|
||||||
|
& 'C:\Program Files\Go\bin\go.exe' run ./cmd/gosentry
|
||||||
|
```
|
||||||
|
|
||||||
|
Linux:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# CGO must stay enabled because the Fyne GUI links against native Linux desktop
|
||||||
|
# libraries.
|
||||||
|
CGO_ENABLED=1 go run ./cmd/gosentry
|
||||||
|
```
|
||||||
|
|
||||||
|
The same environment is required for the test suite — see
|
||||||
|
[TESTS.md](TESTS.md):
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
scripts\test.bat
|
||||||
|
```
|
||||||
|
|
||||||
|
## 4. Building the Executable
|
||||||
|
|
||||||
### Windows
|
### Windows
|
||||||
|
|
||||||
@@ -59,9 +156,8 @@ sudo apt install golang gcc libgl1-mesa-dev xorg-dev
|
|||||||
.\scripts\build-windows.bat
|
.\scripts\build-windows.bat
|
||||||
```
|
```
|
||||||
|
|
||||||
The Windows build is created as a GUI application, so it does not open a terminal window.
|
The Windows build is created as a GUI application, so it does not open a
|
||||||
|
terminal window. The binary is written to:
|
||||||
The binary is written to:
|
|
||||||
|
|
||||||
```text
|
```text
|
||||||
dist\windows\gosentry-<version>-windows-amd64.exe
|
dist\windows\gosentry-<version>-windows-amd64.exe
|
||||||
@@ -97,7 +193,9 @@ The binary is copied to:
|
|||||||
dist/linux/gosentry-<version>-linux-amd64
|
dist/linux/gosentry-<version>-linux-amd64
|
||||||
```
|
```
|
||||||
|
|
||||||
### Release build from Linux
|
## 5. Building a Release
|
||||||
|
|
||||||
|
### All targets from Linux
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Interactively choose Linux amd64, Linux arm64, Windows amd64, or all artifacts
|
# Interactively choose Linux amd64, Linux arm64, Windows amd64, or all artifacts
|
||||||
@@ -123,7 +221,39 @@ dist/linux/gosentry-<version>-linux-arm64
|
|||||||
dist/windows/gosentry-<version>-windows-amd64.exe
|
dist/windows/gosentry-<version>-windows-amd64.exe
|
||||||
```
|
```
|
||||||
|
|
||||||
### Automated release builds (CI)
|
### Packaging
|
||||||
|
|
||||||
|
The `package-*` scripts build the binary for their platform and wrap it in a
|
||||||
|
distributable archive together with `README.md` and `CHANGELOG.md`:
|
||||||
|
|
||||||
|
Windows:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
scripts\package-windows.bat
|
||||||
|
```
|
||||||
|
|
||||||
|
```text
|
||||||
|
dist\windows\gosentry-<version>-windows-amd64.zip
|
||||||
|
```
|
||||||
|
|
||||||
|
Linux:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./scripts/package-linux.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
```text
|
||||||
|
dist/linux/gosentry-<version>-linux-amd64.tar.gz
|
||||||
|
dist/linux/gosentry-<version>-linux-arm64.tar.gz
|
||||||
|
```
|
||||||
|
|
||||||
|
The arm64 archive is produced only when the `aarch64-linux-gnu-gcc` cross
|
||||||
|
compiler is available; otherwise that target is skipped with a message.
|
||||||
|
|
||||||
|
The version stamped into the file names and into the binary comes from
|
||||||
|
`src/app/version.go`.
|
||||||
|
|
||||||
|
## 6. CI
|
||||||
|
|
||||||
Tagged releases are built automatically on both GitHub and Codeberg:
|
Tagged releases are built automatically on both GitHub and Codeberg:
|
||||||
|
|
||||||
@@ -144,14 +274,35 @@ The Windows binary is cross-compiled with MinGW-w64 from the Linux job, so no
|
|||||||
Windows runner is required. Each archive contains the executable plus `README.md`
|
Windows runner is required. Each archive contains the executable plus `README.md`
|
||||||
and `CHANGELOG.md`, matching the local `package-*` scripts.
|
and `CHANGELOG.md`, matching the local `package-*` scripts.
|
||||||
|
|
||||||
To cut a release, bump `src/app/version.go`, then create and publish a release
|
### Cutting a release
|
||||||
with a matching `v` tag on the forge (GitHub Releases / Codeberg releases). You
|
|
||||||
can do that from the web UI or the CLI, e.g.:
|
Before tagging:
|
||||||
|
|
||||||
|
1. Bump `src/app/version.go`. The tag must match it exactly.
|
||||||
|
2. Add the version's [CHANGELOG.md](CHANGELOG.md) section.
|
||||||
|
3. Retake the README screenshots (`images/screenshot_jobs.PNG`,
|
||||||
|
`images/screenshot_settings.PNG`) if the GUI changed its appearance. This is
|
||||||
|
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,
|
||||||
|
so a stale shot advertises an application that no longer exists. Take them
|
||||||
|
from a real build, not from a development run with test data.
|
||||||
|
4. Run `scripts/test.bat` (or `go vet ./... && go test -race ./...`) and push
|
||||||
|
`main`, so the tag lands on a commit the forge actually has.
|
||||||
|
|
||||||
|
Then create and publish a release with a matching `v` tag on the forge (GitHub
|
||||||
|
Releases / Codeberg releases). `origin` is the Gitea repository, and GitHub is a
|
||||||
|
push mirror of it, so the tag is pushed to Gitea and reaches GitHub through the
|
||||||
|
mirror — never created on GitHub directly (see
|
||||||
|
[Releasing through the GitHub push mirror](#releasing-through-the-github-push-mirror)):
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git tag v0.11.5
|
git tag v0.11.5
|
||||||
git push origin v0.11.5 # and to the Codeberg remote
|
git push origin v0.11.5 # Gitea; and to the Codeberg remote
|
||||||
gh release create v0.11.5 --generate-notes # GitHub; publishes the release
|
|
||||||
|
# wait for the mirror, then confirm GitHub actually has the tag
|
||||||
|
git ls-remote --tags https://github.com/mixeme/gosentry.git v0.11.5
|
||||||
|
|
||||||
|
gh release create v0.11.5 --verify-tag --generate-notes # GitHub; publishes the release
|
||||||
```
|
```
|
||||||
|
|
||||||
Publishing the release triggers the workflow: it strips the leading `v` from
|
Publishing the release triggers the workflow: it strips the leading `v` from
|
||||||
@@ -161,66 +312,40 @@ also allows a manual, upload-free build to smoke-test the pipeline.
|
|||||||
|
|
||||||
Codeberg publishing needs a repository secret named `RELEASE_TOKEN` (a Codeberg
|
Codeberg publishing needs a repository secret named `RELEASE_TOKEN` (a Codeberg
|
||||||
access token with the `write:repository` scope) under
|
access token with the `write:repository` scope) under
|
||||||
**Settings → Actions → Secrets**. GitHub uses the built-in `GITHUB_TOKEN`.
|
**Settings → Actions → Secrets**. Without it the build and packaging steps still
|
||||||
|
succeed, but the upload step fails on authentication and takes the job down with
|
||||||
|
it, leaving a published release with no attached assets. GitHub needs no such
|
||||||
|
setup: `softprops/action-gh-release` falls back to the built-in `GITHUB_TOKEN`,
|
||||||
|
and the workflow already grants it `contents: write`.
|
||||||
|
|
||||||
## Run From Source
|
### Releasing through the GitHub push mirror
|
||||||
|
|
||||||
Windows:
|
The GitHub repository `mixeme/gosentry` is not a separate remote you push to; it
|
||||||
|
is a push mirror driven by Gitea. Gitea mirrors with pruning, so every ref that
|
||||||
|
exists on GitHub but not in Gitea is deleted on the next synchronisation.
|
||||||
|
|
||||||
```powershell
|
This is what breaks the obvious way of cutting a GitHub release. `gh release
|
||||||
# Fyne requires CGO on Windows. MSYS2 UCRT64 provides the C compiler and native
|
create v1.0.0` creates the tag on GitHub when it is missing — a tag Gitea has
|
||||||
# libraries used by the desktop backend.
|
never heard of. The next mirror run prunes it, GitHub orphans the release whose
|
||||||
$env:Path = 'C:\msys64\ucrt64\bin;' + $env:Path
|
tag disappeared and turns it into a draft, and the release looks deleted on the
|
||||||
$env:CGO_ENABLED = '1'
|
Releases page. The archives go with it. Nothing reports an error: the workflow
|
||||||
|
ran, the assets uploaded, and the release evaporated afterwards.
|
||||||
|
|
||||||
# go run starts the app from source. Use scripts\build-windows.bat when you need
|
The order that works is therefore:
|
||||||
# a standalone .exe without a console window.
|
|
||||||
& 'C:\Program Files\Go\bin\go.exe' run ./cmd/gosentry
|
|
||||||
```
|
|
||||||
|
|
||||||
Linux:
|
1. `git push origin <tag>` — the tag enters Gitea, which owns it.
|
||||||
|
2. Wait for the mirror, or force it with **Settings → Repository → Mirror
|
||||||
|
Settings → Synchronize Now** in Gitea.
|
||||||
|
3. `git ls-remote --tags https://github.com/mixeme/gosentry.git <tag>` — confirm
|
||||||
|
GitHub has it.
|
||||||
|
4. `gh release create <tag> --verify-tag …` — `--verify-tag` is the guard, not a
|
||||||
|
nicety: without it `gh` silently creates the doomed tag when the mirror has
|
||||||
|
not caught up yet.
|
||||||
|
|
||||||
```bash
|
Release notes and assets are GitHub-side metadata; a mirror push cannot touch
|
||||||
# CGO must stay enabled because the Fyne GUI links against native Linux desktop
|
them, so once the release sits on a mirrored tag, later synchronisations leave
|
||||||
# libraries.
|
it alone. Two consequences follow. Moving a published tag in Gitea force-pushes
|
||||||
CGO_ENABLED=1 go run ./cmd/gosentry
|
it on GitHub and leaves the release pointing at a different commit, and deleting
|
||||||
```
|
a published tag in Gitea destroys the GitHub release along with its uploaded
|
||||||
|
archives — neither is recoverable from the mirror side. Codeberg is unaffected:
|
||||||
## Project Layout
|
its releases live in the same forge as its tags.
|
||||||
|
|
||||||
- `cmd/gosentry` — entry point; starts the desktop app.
|
|
||||||
- `src/domain` — pure value types: `Job`, `Config`, `RunRecord`, `Schedule`, `JobRuntime`.
|
|
||||||
- `src/app` — `Service`: sole owner of job and runtime state; emits typed events to the UI.
|
|
||||||
- `src/scheduler` — pure timing loop; calls `Service.RunDue` on every tick.
|
|
||||||
- `src/runner` — shell command execution, log file writing, and log cleanup.
|
|
||||||
- `src/storage` — JSON persistence (`gosentry.json`, `jobs.json`).
|
|
||||||
- `src/platform/autostart` — `Manager` interface with Windows (shortcut) and Linux (XDG) implementations.
|
|
||||||
- `src/platform/desktop` — desktop entry and icon under XDG data home (Linux only).
|
|
||||||
- `src/platform/winproc` — hidden-window startup flags (Windows only).
|
|
||||||
- `src/ui` — Fyne windows, tabs, and dialogs; reads service state through events.
|
|
||||||
- `assets` — app icons embedded into the application binary.
|
|
||||||
- `scripts` — build helpers.
|
|
||||||
- `docs` — architecture notes, changelog, and roadmap.
|
|
||||||
|
|
||||||
Build outputs are written to `dist/`.
|
|
||||||
|
|
||||||
## Dependencies
|
|
||||||
|
|
||||||
GoSentry keeps the direct dependency list intentionally small:
|
|
||||||
|
|
||||||
- [`fyne.io/fyne/v2`](https://fyne.io/) for the native GUI.
|
|
||||||
- `github.com/robfig/cron/v3` for cron schedule parsing.
|
|
||||||
|
|
||||||
The remaining entries in `go.mod` are indirect dependencies pulled by Fyne and the Go module resolver.
|
|
||||||
|
|
||||||
Source repositories for mirroring:
|
|
||||||
|
|
||||||
- Go toolchain: https://go.googlesource.com/go
|
|
||||||
- Fyne: https://github.com/fyne-io/fyne
|
|
||||||
- robfig/cron: https://github.com/robfig/cron
|
|
||||||
|
|
||||||
To list every direct and indirect Go module used by the current checkout:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
go list -m all
|
|
||||||
```
|
|
||||||
|
|||||||
@@ -81,6 +81,50 @@ Design notes / open questions:
|
|||||||
Service exposes import/export operations; the UI only picks the file and
|
Service exposes import/export operations; the UI only picks the file and
|
||||||
shows the outcome.
|
shows the outcome.
|
||||||
|
|
||||||
|
### Split the files that are over the size guideline
|
||||||
|
|
||||||
|
[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
|
||||||
|
examples. Six non-test files are over it at 1.0.0, including both files that
|
||||||
|
were already split once:
|
||||||
|
|
||||||
|
| File | Lines |
|
||||||
|
|------|-------|
|
||||||
|
| `src/app/operations.go` | 490 |
|
||||||
|
| `src/ui/jobs_view.go` | 355 |
|
||||||
|
| `src/app/run.go` | 287 |
|
||||||
|
| `src/ui/history_view.go` | 282 |
|
||||||
|
| `src/ui/settings_view.go` | 277 |
|
||||||
|
| `src/storage/store.go` | 265 |
|
||||||
|
|
||||||
|
This is deliberately deferred to the next whole-project review rather than done
|
||||||
|
piecemeal: [REVIEW.md](REVIEW.md) already asks item 2 to look for exactly this,
|
||||||
|
a split touches every reader of the file, and doing all six in one pass keeps
|
||||||
|
the seams consistent instead of settling them six different ways. Splitting is
|
||||||
|
also the kind of change that reads as pure movement while quietly dropping a
|
||||||
|
function, so it wants one careful pass, not six hurried ones.
|
||||||
|
|
||||||
|
Seams visible today, as a starting point rather than a decision:
|
||||||
|
|
||||||
|
- **`operations.go`** — the worst overage and the clearest split: the public
|
||||||
|
mutating operations (`CreateJob` … `UpdateSettings`), the `…Locked` state
|
||||||
|
helpers that only they call, and the pure validators and normalizers
|
||||||
|
(`normalizeJob`, `validateJob`, `hasFileName`, `validateConfig`) are three
|
||||||
|
distinct jobs already sitting in three consecutive blocks.
|
||||||
|
- **`history_view.go`** — the column-measuring helpers (`textWidth` through
|
||||||
|
`historyColumnWidths`) are pure, already unit-tested, and independent of the
|
||||||
|
table they size.
|
||||||
|
- **`jobs_view.go`** — nearly all of it is one `newJobsView` constructor, so the
|
||||||
|
split has to break that function up (list template, toolbar handlers,
|
||||||
|
assembly) rather than move whole functions. Larger judgement call than the
|
||||||
|
others.
|
||||||
|
- **`run.go`**, **`settings_view.go`**, **`store.go`** — barely over. Worth
|
||||||
|
re-measuring at the time; if a pass elsewhere has shrunk them, leave them
|
||||||
|
alone rather than splitting for the sake of the number.
|
||||||
|
|
||||||
|
Scope note: the guideline is about source files. Test files are much larger and
|
||||||
|
that is fine — a table-driven test file grows with the cases it covers.
|
||||||
|
|
||||||
### Window size persistence *(frozen)*
|
### Window size persistence *(frozen)*
|
||||||
|
|
||||||
Window size is currently **not** saved on quit or close. Saving was disabled
|
Window size is currently **not** saved on quit or close. Saving was disabled
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ package ui
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fyne.io/fyne/v2"
|
"fyne.io/fyne/v2"
|
||||||
|
"fyne.io/fyne/v2/container"
|
||||||
|
"fyne.io/fyne/v2/layout"
|
||||||
"fyne.io/fyne/v2/theme"
|
"fyne.io/fyne/v2/theme"
|
||||||
"fyne.io/fyne/v2/widget"
|
"fyne.io/fyne/v2/widget"
|
||||||
)
|
)
|
||||||
@@ -59,6 +61,17 @@ func (l minWidthLayout) Layout(objects []fyne.CanvasObject, size fyne.Size) {
|
|||||||
// than hard-coded so it follows a theme that changes SizeNameInnerPadding.
|
// than hard-coded so it follows a theme that changes SizeNameInnerPadding.
|
||||||
func rowOverlap() float32 { return -theme.InnerPadding() }
|
func rowOverlap() float32 { return -theme.InnerPadding() }
|
||||||
|
|
||||||
|
// cancelRowOverlap exempts one row from the rowOverlap() spacing of the section
|
||||||
|
// it sits in, by padding its top edge with exactly what rowOverlap takes away.
|
||||||
|
// The overlap assumes both neighbours are text rows: each insets its text, so
|
||||||
|
// one padding's worth is duplicated and can go. A row whose value paints its own
|
||||||
|
// box to the row's edge — a Select, an Entry, a Button — has no such inset, so
|
||||||
|
// the overlap eats the visible gap instead and the box ends up flush against the
|
||||||
|
// row above it.
|
||||||
|
func cancelRowOverlap(row fyne.CanvasObject) fyne.CanvasObject {
|
||||||
|
return container.New(layout.NewCustomPaddedLayout(-rowOverlap(), 0, 0, 0), row)
|
||||||
|
}
|
||||||
|
|
||||||
// initialSplitOffset returns the container.Split offset that opens a horizontal
|
// initialSplitOffset returns the container.Split offset that opens a horizontal
|
||||||
// split with its leading pane at the given natural width. SetOffset takes a
|
// split with its leading pane at the given natural width. SetOffset takes a
|
||||||
// ratio, but a pane's natural width is absolute, so the ratio is derived from
|
// ratio, but a pane's natural width is absolute, so the ratio is derived from
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
|
|
||||||
"fyne.io/fyne/v2/test"
|
"fyne.io/fyne/v2/test"
|
||||||
"fyne.io/fyne/v2/theme"
|
"fyne.io/fyne/v2/theme"
|
||||||
|
"fyne.io/fyne/v2/widget"
|
||||||
)
|
)
|
||||||
|
|
||||||
// TestRowOverlapMatchesInnerPadding pins rowOverlap to theme.InnerPadding, the
|
// TestRowOverlapMatchesInnerPadding pins rowOverlap to theme.InnerPadding, the
|
||||||
@@ -27,6 +28,37 @@ func TestRowOverlapMatchesInnerPadding(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestCancelRowOverlapAddsBackOneInnerPadding is the regression guard for the
|
||||||
|
// Settings tab's Theme row sitting flush against the Notifications checkbox:
|
||||||
|
// the wrapper must add exactly the padding rowOverlap removes, on the top edge
|
||||||
|
// only, so the row below is unaffected and the width does not change.
|
||||||
|
func TestCancelRowOverlapAddsBackOneInnerPadding(t *testing.T) {
|
||||||
|
testApp := test.NewApp()
|
||||||
|
defer testApp.Quit()
|
||||||
|
|
||||||
|
child := widget.NewSelect([]string{"System"}, nil)
|
||||||
|
wrapped := cancelRowOverlap(child)
|
||||||
|
|
||||||
|
childMin, wrappedMin := child.MinSize(), wrapped.MinSize()
|
||||||
|
if got, want := wrappedMin.Height, childMin.Height-rowOverlap(); got != want {
|
||||||
|
t.Errorf("wrapped height = %v, want %v (child %v plus one inner padding)", got, want, childMin.Height)
|
||||||
|
}
|
||||||
|
if got, want := wrappedMin.Width, childMin.Width; got != want {
|
||||||
|
t.Errorf("wrapped width = %v, want the child's %v", got, want)
|
||||||
|
}
|
||||||
|
|
||||||
|
wrapped.Resize(wrappedMin)
|
||||||
|
if got, want := child.Position().Y, -rowOverlap(); got != want {
|
||||||
|
t.Errorf("child Y = %v, want %v", got, want)
|
||||||
|
}
|
||||||
|
if got := child.Position().X; got != 0 {
|
||||||
|
t.Errorf("child X = %v, want 0", got)
|
||||||
|
}
|
||||||
|
if got, want := child.Size().Height, childMin.Height; got != want {
|
||||||
|
t.Errorf("child height = %v, want %v: the padding must not be taken out of the row", got, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// TestCaptionColumnWidth covers the shapes F10's shared helper has to handle:
|
// TestCaptionColumnWidth covers the shapes F10's shared helper has to handle:
|
||||||
// no captions, one, and several of varying length at two text sizes.
|
// no captions, one, and several of varying length at two text sizes.
|
||||||
func TestCaptionColumnWidth(t *testing.T) {
|
func TestCaptionColumnWidth(t *testing.T) {
|
||||||
|
|||||||
@@ -60,7 +60,11 @@ func newSettingsLayout(f settingsFormFields) fyne.CanvasObject {
|
|||||||
settingsRow(capW, "", f.autostartStatus),
|
settingsRow(capW, "", f.autostartStatus),
|
||||||
settingsRow(capW, "Tray", f.minimizeToTray),
|
settingsRow(capW, "Tray", f.minimizeToTray),
|
||||||
settingsRow(capW, "Notifications", f.notifications),
|
settingsRow(capW, "Notifications", f.notifications),
|
||||||
settingsRow(capW, "Theme", f.themeSelect),
|
// Theme is the one row here whose value is not text: the Select paints
|
||||||
|
// a box out to the row's edge, so the section's overlap would leave it
|
||||||
|
// flush against the Notifications checkbox. Cancelling the overlap for
|
||||||
|
// this row alone restores the gap the checkbox rows have.
|
||||||
|
cancelRowOverlap(settingsRow(capW, "Theme", f.themeSelect)),
|
||||||
),
|
),
|
||||||
widget.NewSeparator(),
|
widget.NewSeparator(),
|
||||||
// Queue used to inline its own container.NewVBox at the theme's default
|
// Queue used to inline its own container.NewVBox at the theme's default
|
||||||
|
|||||||
Reference in New Issue
Block a user