From 22352c57486c4e887805ab9797952cad58b95740 Mon Sep 17 00:00:00 2001 From: rouggy Date: Mon, 20 Jul 2026 19:17:13 +0200 Subject: [PATCH] =?UTF-8?q?fix:=20who's-on-air=20freq/appearance=20lag=20?= =?UTF-8?q?=E2=80=94=20prompt=20publish=20on=20activity=20+=205s=20widget?= =?UTF-8?q?=20poll?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit publishLiveStatus only ran on QSO log or the 15s heartbeat, and the widget polled every 15s, so an operator's band/freq (and their appearance) could trail by up to a minute. Now ReportLiveActivity debounce-publishes ~1.5s after a freq/band/mode change, and the widget polls every 5s (and 1.5s after qso:logged). --- app.go | 1 + frontend/src/App.tsx | 4 ++-- livestatus.go | 17 +++++++++++++++-- 3 files changed, 18 insertions(+), 4 deletions(-) diff --git a/app.go b/app.go index 78d15f6..c96cb48 100644 --- a/app.go +++ b/app.go @@ -508,6 +508,7 @@ type App struct { liveFreqHz int64 // last freq/band/mode the UI reported (fallback when CAT is off) liveBand string liveMode string + livePublishTimer *time.Timer // debounced live-status publish on activity change liveLastQSOAt time.Time // when this operator last logged a NEW contact — drives online/offline awardSnapMu sync.Mutex // guards the award QSO snapshot awardSnap []qso.QSO // light-scanned + enriched logbook snapshot reused across award computations diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index edbdb48..f3b30a1 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -451,9 +451,9 @@ export default function App() { if (!liveStationsOn) { setLiveStations([]); return; } const load = () => GetLiveStations().then((s) => setLiveStations((s ?? []) as LiveStation[])).catch(() => {}); load(); - const id = window.setInterval(load, 15 * 1000); + const id = window.setInterval(load, 5 * 1000); // Small delay lets the async publishLiveStatus MySQL write land before we read. - const offLogged = EventsOn('qso:logged', () => { window.setTimeout(load, 2000); }); + const offLogged = EventsOn('qso:logged', () => { window.setTimeout(load, 1500); }); return () => { window.clearInterval(id); offLogged?.(); }; }, [liveStationsOn]); // Mode OpsLog shows when the rig reports generic DIG_U/DIG_L. OmniRig diff --git a/livestatus.go b/livestatus.go index ffe04df..292f81e 100644 --- a/livestatus.go +++ b/livestatus.go @@ -159,10 +159,23 @@ func (a *App) liveStatusOperator() (op, station string) { // ReportLiveActivity is called by the UI with the current entry-strip freq/band/ // mode, used as a fallback for live status when the CAT isn't connected. func (a *App) ReportLiveActivity(freqHz int64, band, mode string) { + nb := strings.ToUpper(strings.TrimSpace(band)) + nm := strings.ToUpper(strings.TrimSpace(mode)) a.liveActMu.Lock() + changed := a.liveFreqHz != freqHz || a.liveBand != nb || a.liveMode != nm a.liveFreqHz = freqHz - a.liveBand = strings.ToUpper(strings.TrimSpace(band)) - a.liveMode = strings.ToUpper(strings.TrimSpace(mode)) + a.liveBand = nb + a.liveMode = nm + // Push a fresh row PROMPTLY when the operator's freq/band/mode changes, instead + // of waiting for the next 15 s heartbeat — otherwise "who's on air" showed a + // stale band and took up to a minute to catch up. Debounced so spinning the VFO + // doesn't hammer the shared MySQL. + if changed { + if a.livePublishTimer != nil { + a.livePublishTimer.Stop() + } + a.livePublishTimer = time.AfterFunc(1500*time.Millisecond, func() { a.publishLiveStatus() }) + } a.liveActMu.Unlock() }