From c0a52a9040419ef55325a410e28870d5e5ffbfe5 Mon Sep 17 00:00:00 2001 From: Kharec Date: Thu, 15 Oct 2026 17:43:56 +0200 Subject: [PATCH] fix: protect the dirty flag with an atomic.Bool SetDirty runs on a Wails call goroutine while the GTK close handler reads the flag, with no synchronisation: an edit landing while a save finished could make the close confirmation unreliable. The spellMu comment already documents the goroutine-per-call convention; the flag that guards against losing work deserves the same care. --- CHANGELOG.md | 5 +++++ app.go | 12 ++++++++---- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 26b0e67..ebe06bd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,11 @@ version is in the `VERSION` file. ## [Unreleased] +### Fixed + +- The unsaved-changes marker is now updated atomically: an edit landing while a save + finished could make the close confirmation unreliable. + ## [1.4] - 2026-10-15 ### Fixed diff --git a/app.go b/app.go index bcc6bb0..331a7b8 100644 --- a/app.go +++ b/app.go @@ -10,6 +10,7 @@ import ( "slices" "strings" "sync" + "sync/atomic" "time" "github.com/wailsapp/wails/v2/pkg/runtime" @@ -24,8 +25,11 @@ import ( // App is bound to the frontend: every exported method is callable from JS. type App struct { - ctx context.Context - dirty bool + ctx context.Context + // dirty decides whether closing the window asks for confirmation. It is + // written from Wails call goroutines (SetDirty) and read by the GTK + // close handler: atomic, like spellMu protects the checker. + dirty atomic.Bool initialPath string // Wails runs each call on its own goroutine: spelling calls share one @@ -74,7 +78,7 @@ func (a *App) startup(ctx context.Context) { // beforeClose keeps the window open while there are unsaved changes and // lets the frontend ask; it quits again once the user has decided. func (a *App) beforeClose(ctx context.Context) bool { - if !a.dirty { + if !a.dirty.Load() { return false } runtime.EventsEmit(a.ctx, "confirm-close") @@ -201,7 +205,7 @@ func (a *App) RevisionSet(n int) screenplay.Revision { return r } -func (a *App) SetDirty(dirty bool) { a.dirty = dirty } +func (a *App) SetDirty(dirty bool) { a.dirty.Store(dirty) } // dataDir is the XDG data directory (~/.local/share). func dataDir() string {