fix: theme sometimes reverts on reopen — gate per-profile UI prefs on scope

The theme (and other per-profile UI prefs) are read via a.settings.Get, which is
scoped to the active profile. GetUIPref only guarded on a.settings==nil, so in
the startup window AFTER NewStore but BEFORE SetProfile(active) it resolved with
the wrong (empty) scope. The frontend theme self-heal treats a resolved ""  as
"answered, unset → keep default, stop retrying", so a race landed on the light
default and stayed. Add a settingsScoped atomic flag set right after
SetProfile; GetUIPref/SetUIPref return "not ready" until then, so the frontend
keeps retrying and restores the real theme. Also prevents seeding prefs into the
wrong profile scope. Changelog entry added to 0.21.0 (EN+FR).
This commit is contained in:
2026-07-24 19:14:26 +02:00
parent fd097a647f
commit 3b1a8ef01a
2 changed files with 13 additions and 7 deletions
+9 -5
View File
@@ -515,6 +515,7 @@ type App struct {
pttKeyedMethod string // "cat" | "rts" | "dtr" while keyed; "" when idle
pttGen int64 // bumped on every key; a delayed unkey only fires if unchanged (guards against a stale release cutting a new transmission)
startupErr string // captured for surfacing to the frontend
settingsScoped atomic.Bool // true once a.settings is scoped to the active profile — GetUIPref/SetUIPref (per-profile) must wait for it, else an early call reads the wrong scope and e.g. resets the theme
dbPath string // settings/config database file (settings + profiles); may be a user-chosen location
logbookPath string // default SQLite logbook file (QSOs), next to the settings db — used when a profile doesn't point elsewhere
logDb *sql.DB // QSO logbook connection — MySQL, a per-profile SQLite file, or the default logbook.db (never the settings db, except on fallback)
@@ -848,6 +849,7 @@ func (a *App) startup(ctx context.Context) {
}
}
a.settings.SetProfile(active.ID)
a.settingsScoped.Store(true) // per-profile settings reads (GetUIPref…) are now safe
// US county resolver — its own local SQLite (data/uls.db), populated on demand
// by DownloadULSCounties. Opening (creating an empty store) is cheap and never
// fatal: county resolution simply stays inert until the operator downloads it.
@@ -2013,10 +2015,12 @@ func (a *App) groupDigitalSlots() bool {
}
func (a *App) GetUIPref(key string) (string, error) {
if a.settings == nil {
if a.settings == nil || !a.settingsScoped.Load() {
// Distinct from a genuinely-empty pref: the (LOCAL SQLite) settings store
// isn't wired yet. There's a brief window at launch where the frontend can
// call this before OnStartup has opened the DB and built the store. The UI
// isn't wired AND scoped to the active profile yet. There's a brief window at
// launch where the frontend can call this before it's ready — reading then
// returns the wrong profile's (empty) value, which stopped the theme
// self-heal early ("dark theme reverts to light on reopen"). The UI
// uses the error to keep RETRYING rather than treat it as "unset" and fall
// back to a default — the "dark theme reverts to light after an update" bug
// (the update cleared localStorage, and the DB read gave up too early while
@@ -2027,8 +2031,8 @@ func (a *App) GetUIPref(key string) (string, error) {
}
func (a *App) SetUIPref(key, value string) error {
if a.settings == nil {
return fmt.Errorf("db not initialized")
if a.settings == nil || !a.settingsScoped.Load() {
return fmt.Errorf("settings store not ready") // avoid seeding the wrong profile scope
}
return a.settings.Set(a.ctx, "ui."+key, value)
}
+4 -2
View File
@@ -4,11 +4,13 @@
"date": "2026-07-24",
"en": [
"⚠️ IMPORTANT — ARCHITECTURE CHANGE. OpsLog now keeps your settings/profiles and your QSO logbook in SEPARATE database files (before, everything was in one file). On the first launch after this update, your existing database is split AUTOMATICALLY and non-destructively: your contacts are copied into a new logbook file (logbook.db) while the originals stay untouched in the settings database as a backup — nothing is deleted, no QSO is lost. Existing installs keep their opslog.db as the settings database; fresh installs name it settings.db. As a precaution, back up your OpsLog data folder before updating. Afterwards, Settings → Database shows the settings database and this profile's logbook as two separate sections.",
"Your QSOs and your settings now live in separate files: settings + profiles stay in the settings database, while contacts go to a dedicated logbook file (existing logs are migrated automatically, originals kept as a backup). A profile can also point at its own logbook file — ideal for a visiting operator, whose contacts stay out of your log without ever touching your settings or profiles. The Database panel now clearly shows the two, with an 'Open folder' shortcut, and a logbook file can be renamed/relocated (its QSOs move with it)."
"Your QSOs and your settings now live in separate files: settings + profiles stay in the settings database, while contacts go to a dedicated logbook file (existing logs are migrated automatically, originals kept as a backup). A profile can also point at its own logbook file — ideal for a visiting operator, whose contacts stay out of your log without ever touching your settings or profiles. The Database panel now clearly shows the two, with an 'Open folder' shortcut, and a logbook file can be renamed/relocated (its QSOs move with it).",
"Fixed the colour theme occasionally reverting on reopen. A startup race let the theme be read before the settings store was tied to your profile, so it briefly came back empty and the app kept the default — now it waits for the profile so your theme is restored reliably."
],
"fr": [
"⚠️ IMPORTANT — CHANGEMENT D'ARCHITECTURE. OpsLog stocke désormais tes réglages/profils et ton journal de QSO dans des fichiers de base de données SÉPARÉS (avant, tout était dans un seul fichier). Au premier lancement après cette mise à jour, ta base existante est scindée AUTOMATIQUEMENT et sans destruction : tes contacts sont copiés dans un nouveau fichier journal (logbook.db) tandis que les originaux restent intacts dans la base de réglages en sauvegarde — rien n'est supprimé, aucun QSO n'est perdu. Les installs existantes gardent leur opslog.db comme base de réglages ; les nouvelles installs la nomment settings.db. Par précaution, sauvegarde ton dossier de données OpsLog avant de mettre à jour. Ensuite, Réglages → Base de données affiche la base de réglages et le journal de ce profil en deux sections distinctes.",
"Tes QSO et tes réglages sont désormais dans des fichiers séparés : réglages + profils dans la base de réglages, contacts dans un fichier journal dédié (les journaux existants sont migrés automatiquement, les originaux gardés en sauvegarde). Un profil peut aussi pointer vers son propre fichier journal — idéal pour un opérateur de passage, dont les contacts restent hors de ton journal sans jamais toucher tes réglages ni profils. Le panneau Base de données montre maintenant clairement les deux, avec un raccourci « Ouvrir le dossier », et un fichier journal peut être renommé/déplacé (ses QSO le suivent)."
"Tes QSO et tes réglages sont désormais dans des fichiers séparés : réglages + profils dans la base de réglages, contacts dans un fichier journal dédié (les journaux existants sont migrés automatiquement, les originaux gardés en sauvegarde). Un profil peut aussi pointer vers son propre fichier journal — idéal pour un opérateur de passage, dont les contacts restent hors de ton journal sans jamais toucher tes réglages ni profils. Le panneau Base de données montre maintenant clairement les deux, avec un raccourci « Ouvrir le dossier », et un fichier journal peut être renommé/déplacé (ses QSO le suivent).",
"Correction du thème qui revenait parfois au défaut à la réouverture. Une course au démarrage laissait lire le thème avant que le store de réglages soit rattaché à ton profil : il revenait vide un instant et l'app gardait le défaut — il attend maintenant le profil, donc ton thème est restauré de façon fiable."
]
},
{