From ede9461010da20114b75397d456b357f24d42de5 Mon Sep 17 00:00:00 2001 From: rouggy Date: Thu, 23 Jul 2026 19:13:00 +0200 Subject: [PATCH] fix: theme reverts to light after an update when the logbook DB is slow MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit An update can clear the WebView's localStorage; the theme is then restored from the portable DB pref, but GetUIPref returned ("", nil) while the settings store was still starting (indistinguishable from a genuinely-unset pref), so the self-heal gave up after ~2.4s and fell back to the light default. GetUIPref now returns an error when the settings store isn't wired yet, and the theme self-heal treats that as "not ready → keep retrying" (up to ~36s) vs a real empty answer ("unset → keep default"). A dark theme survives the relaunch. --- app.go | 8 +++++++- changelog.json | 2 ++ frontend/src/lib/theme.tsx | 13 ++++++++++--- 3 files changed, 19 insertions(+), 4 deletions(-) diff --git a/app.go b/app.go index c8d11ad..dbe47e4 100644 --- a/app.go +++ b/app.go @@ -1835,7 +1835,13 @@ func (a *App) groupDigitalSlots() bool { func (a *App) GetUIPref(key string) (string, error) { if a.settings == nil { - return "", nil + // Distinct from a genuinely-empty pref: the settings store isn't wired yet + // (still connecting the logbook DB — can take seconds on remote MySQL). 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 — localStorage was cleared by the update, and the DB read gave up too + // early while the backend was still starting). + return "", fmt.Errorf("settings store not ready") } return a.settings.Get(a.ctx, "ui."+key) } diff --git a/changelog.json b/changelog.json index b9513d7..efd4a54 100644 --- a/changelog.json +++ b/changelog.json @@ -3,6 +3,7 @@ "version": "0.20.11", "date": "2026-07-23", "en": [ + "Fixed the colour theme sometimes resetting to light after an update/relaunch: an update can clear the WebView's localStorage, and the fallback that restores the theme from the portable database gave up after ~2.4s — too soon when the logbook DB is still connecting (remote MySQL). It now keeps retrying until the settings store is actually ready, so a dark theme is reliably restored.", "Fixed clicking a spot / band-map entry not moving the radio on Yaesu (and other non-Icom) rigs controlled through OmniRig — e.g. the FT-891. OpsLog tuned via OmniRig's SetSimplexMode, which on these rigs returns success but silently doesn't move the VFO (mode changes worked, so it looked like a puzzle). It now also writes the VFO frequency property directly, which does move them. Icom rigs are unchanged (there SetSimplexMode is the reliable path).", "New CW keyer engine: 'Serial port (DTR=CW / RTS=PTT)'. OpsLog can now key CW by toggling a serial control line itself — the hardware-keying method N1MM/WSJT use with a Yaesu SCU-17 and generic keying interfaces — without a K1EL WinKeyer. Settings → CW Keyer → Keyer engine: pick 'Serial port', choose the keying COM port and whether CW is on DTR (PTT on RTS) or swapped. Speed, Farnsworth, lead-in/tail and PTT keying all apply; macros, auto-CQ and work exactly as with the WinKeyer. An 'Invert keying (active-LOW)' option is there for interfaces that key backwards / transmit at rest.", "Fixed the spectrum scope never displaying on the IC-7300: its CI-V waveform frames actually carry the same leading main-scope selector byte as the dual-scope IC-7610/9700, but OpsLog assumed the 7300 omitted it — so it read the frame sequence number from the wrong byte (always 0), discarded every frame, and drew nothing. The frame layout is now detected from the data itself, so the 7300 (and other single-scope Icoms) render correctly, in both center and fixed modes. The IC-7610/9700 are unaffected.", @@ -10,6 +11,7 @@ "Closing OpsLog no longer switches off the scope display on the radio itself: turning the scope off used to send both 'stop CI-V data' and 'display off', so quitting blanked a local IC-7300's own scope screen. It now only stops the CI-V data stream on disable and never touches the radio's display (the display-on command is sent solely when enabling)." ], "fr": [ + "Correction du thème de couleur qui revenait parfois au clair après une mise à jour/relancement : une mise à jour peut vider le localStorage de la WebView, et le repli qui restaure le thème depuis la base portable abandonnait après ~2,4s — trop tôt quand la base logbook est encore en connexion (MySQL distant). Il réessaie maintenant jusqu'à ce que le store de réglages soit réellement prêt, donc un thème sombre est restauré de façon fiable.", "Correction : cliquer un spot / une entrée de band-map ne changeait pas la fréquence sur les Yaesu (et autres radios non-Icom) pilotées via OmniRig — p. ex. le FT-891. OpsLog accordait via SetSimplexMode d'OmniRig, qui sur ces radios renvoie « OK » mais ne bouge pas le VFO (le changement de mode marchait, d'où l'énigme). Il écrit maintenant aussi directement la propriété fréquence du VFO, ce qui les fait bien QSY. Les Icom sont inchangés (là, SetSimplexMode reste la bonne méthode).", "Nouveau moteur de keyer CW : « Port série (DTR=CW / RTS=PTT) ». OpsLog peut désormais manipuler la CW en basculant lui-même une ligne de contrôle série — la méthode de keying matériel qu'utilisent N1MM/WSJT avec un Yaesu SCU-17 et les interfaces génériques — sans WinKeyer K1EL. Réglages → Keyer CW → Moteur : choisis « Port série », le port COM de keying et si la CW est sur DTR (PTT sur RTS) ou l'inverse. Vitesse, Farnsworth, lead-in/tail et PTT s'appliquent ; les macros, l'auto-CQ et fonctionnent exactement comme avec le WinKeyer. Une option « Inverser le keying (actif-BAS) » est prévue pour les interfaces qui manipulent à l'envers / émettent au repos.", "Correction du scope spectral qui ne s'affichait jamais sur l'IC-7300 : ses trames de forme d'onde CI-V portent en réalité le même octet sélecteur de scope en tête que les IC-7610/9700 (dual-scope), mais OpsLog supposait que le 7300 ne l'envoyait pas — il lisait donc le numéro de séquence de la trame sur le mauvais octet (toujours 0), jetait toutes les trames et ne dessinait rien. La disposition des trames est maintenant détectée à partir des données elles-mêmes, donc le 7300 (et les autres Icom mono-scope) s'affichent correctement, en mode centre comme en fixe. Les IC-7610/9700 ne sont pas affectés.", diff --git a/frontend/src/lib/theme.tsx b/frontend/src/lib/theme.tsx index 754d7fa..be7f3a2 100644 --- a/frontend/src/lib/theme.tsx +++ b/frontend/src/lib/theme.tsx @@ -73,16 +73,23 @@ export function ThemeProvider({ children }: { children: ReactNode }) { const load = () => { tries += 1; GetUIPref(LS_KEY).then((raw) => { + // Backend ANSWERED (settings store ready). Apply a valid stored value; an + // empty/invalid one means the theme is genuinely unset → keep the default. + // Either way we're done — do NOT retry (retrying only matters while the + // backend is still starting, which now surfaces as a rejected promise). if (cancelled || userPicked.current) return; const v = raw as ThemeChoice; if (v && ALL.includes(v)) { try { localStorage.setItem(LS_KEY, v); } catch { /* quota */ } applyThemeToDom(v); // idempotent — safe to call unconditionally setThemeState(v); - return; // restored } - if (tries < 8) window.setTimeout(load, 300); // empty (unset or backend not ready yet) → retry - }).catch(() => { if (!cancelled && tries < 8) window.setTimeout(load, 300); }); + }).catch(() => { + // Settings store not ready yet — on a slow / remote logbook this can take + // many seconds. Keep retrying well past the old 2.4s cap so a dark theme + // isn't lost to the light default after an update cleared localStorage. + if (!cancelled && !userPicked.current && tries < 120) window.setTimeout(load, 300); + }); }; load(); return () => { cancelled = true; };