Refactoring complete: v0.4.0 architectural milestone (#1)

## Summary

Completed Phase 5 refactoring and reached the target architecture.

**Architectural milestone achieved:**
- Service layer owns all state and is the sole writer
- UI is a thin Fyne view, all widget updates marshaled via `fyne.Do`
- Core engines are stateless and injectable
- Domain types are pure (no `yaml:"-"` fields)
- Full module builds and `go vet ./...` clean

## Changes

- Bump version: 0.3.6 → 0.4.0
- Update CHANGELOG with Phase 5 summary
- Add ROADMAP "Refactoring Follow-Ups" section

## Known follow-up work

1. **Linux test build broken** — `runner_test.go` needs `//go:build windows` tag
2. **File-size limits exceeded** — `operations.go` (486 lines), `jobs_view.go` (415 lines)

See ROADMAP.md for details.

---------

Co-authored-by: mixeme <mix.public@ya.ru>
Reviewed-on: #1
This commit was merged in pull request #1.
This commit is contained in:
2026-06-22 08:05:10 +03:00
parent d24211cab2
commit 01fd572a89
74 changed files with 5424 additions and 2712 deletions
+60
View File
@@ -0,0 +1,60 @@
package ui
import (
"time"
"gitea.mixdep.ru/mix/gosentry/assets"
"gitea.mixdep.ru/mix/gosentry/src/app"
"fyne.io/fyne/v2"
fyneapp "fyne.io/fyne/v2/app"
)
const appID = "ru.mixdep.gosentry.desktop"
// Run is the application entry point. It owns the process lifecycle — single
// instance arbitration, Fyne app + window construction, tray wiring, and the
// startup-timing record — and delegates all view construction to newMainView in
// mainwindow.go. Keeping lifecycle here and the view there is the run.go /
// mainwindow.go split introduced in T4.1.
func Run(startInTray bool) {
started := time.Now()
instanceListener, primary := acquireSingleInstance(!startInTray)
if !primary {
return
}
if instanceListener != nil {
defer instanceListener.Close()
}
// A stable app ID lets Fyne persist desktop preferences consistently across
// launches and gives tray/window integration a predictable identity.
a := fyneapp.NewWithID(appID)
a.SetIcon(loadAppIcon())
w := a.NewWindow("GoSentry " + app.Version)
configureSystemTray(a, w)
w.Resize(fyne.NewSize(1120, 720))
content, recordStartup := newMainView(w)
w.SetContent(content)
serveSingleInstance(instanceListener, w)
if startInTray {
// Autostart launches intentionally stay hidden, so "window shown" would be
// a misleading metric. Record a separate startup event for the tray path
// instead of forcing one timing definition onto two different UX flows.
recordStartup(time.Since(started), false)
a.Run()
return
}
// Show the window before recording startup time. Measuring earlier, during
// widget construction, looked cheaper in History than the user-perceived
// startup really was. The current point is less abstract: it ends when the
// window has actually been handed to the desktop for display.
w.Show()
recordStartup(time.Since(started), true)
a.Run()
}
func loadAppIcon() fyne.Resource {
return assets.Icon()
}