From 46d6531cf55e47089d98313434d51eb7995fa948 Mon Sep 17 00:00:00 2001 From: Gregory Salaun Date: Sun, 16 Aug 2026 10:48:38 +0200 Subject: [PATCH] fix(qso list): the grid followed the upload, not the log MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reported as delayed auto-upload breaking Recent QSOs. It does not — the grid was waiting on the upload for its news. Nothing reloaded the list on qso:logged. The entry form calls refresh() itself after AddQSO, so a QSO TYPED in OpsLog always appeared; a QSO logged from anywhere else — WSJT-X or MSHV over UDP, JTAlert, NET Control, an offline replay — reached the grid only when something else reloaded it, and the something else was extsvc:uploaded. With auto-upload set to immediate that arrives a second later and hides the gap completely. Set a service to delayed and the QSO appeared one to two minutes late; set it to upload on close and not until the next launch. So the very thing deferring an upload exists for — correcting a contact before it goes out — was impossible, because there was nothing on screen to correct. The list now follows the log, on the same debounced path the upload events already use: qso:logged fires twice per contact (insert, then award refs) and a contest run fires it every few seconds. --- changelog.json | 6 ++++-- frontend/src/App.tsx | 26 ++++++++++++++++++++++---- 2 files changed, 26 insertions(+), 6 deletions(-) diff --git a/changelog.json b/changelog.json index cbb91e4..466ff0b 100644 --- a/changelog.json +++ b/changelog.json @@ -3,10 +3,12 @@ "version": "0.25.6", "date": "", "en": [ - "Right-click: update the US county of the selected contacts from the ULS database, replacing a county since renamed or abolished." + "Right-click: update the US county of the selected contacts from the ULS database, replacing a county since renamed or abolished.", + "A QSO logged from WSJT-X, MSHV or a net now appears in Recent QSOs at once, instead of waiting for a delayed auto-upload to send it." ], "fr": [ - "Clic droit : mettre à jour le comté US des contacts sélectionnés depuis la base ULS, pour remplacer un comté renommé ou supprimé." + "Clic droit : mettre à jour le comté US des contacts sélectionnés depuis la base ULS, pour remplacer un comté renommé ou supprimé.", + "Un QSO logué depuis WSJT-X, MSHV ou un net apparaît aussitôt dans les QSO récents, sans attendre l’envoi d’un upload automatique différé." ] }, { diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 9c1e865..a6d3e38 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -2227,16 +2227,34 @@ export default function App() { } }, [buildActiveFilter]); - // Refresh the Recent QSOs grid after external-service uploads stamp the - // sent status (auto-upload via extsvc:uploaded, or manual QSL Manager via - // qslmgr:done). Debounced so a batch of per-QSO events triggers one reload. + // Reload the Recent QSOs grid when the log changes. + // + // qso:logged is the important one, and it was missing. The entry form calls + // refresh() itself after AddQSO, so a QSO TYPED here always appeared — but a + // QSO logged from anywhere else (WSJT-X / MSHV over UDP, JTAlert, NET + // Control, an offline replay) only reached the grid when something else + // happened to reload it, and the something else was the upload: with + // auto-upload set to immediate, extsvc:uploaded arrived a second later and + // hid the gap entirely. + // + // Which is why this surfaced as "delayed upload breaks the recent list". It + // does not — the grid was waiting on the upload for its news. On a delayed + // service the QSO appeared one to two minutes late, and on close-of-session + // upload not until the next launch: the corrections that deferring the upload + // exists to allow could not be made, because there was nothing to correct on + // screen. The list follows the LOG now, and the upload only stamps a status. + // + // Debounced: qso:logged fires twice per contact (once on insert, once when + // the award refs are materialised), and a contest run fires it every few + // seconds. useEffect(() => { let t: number | undefined; const ping = () => { if (t) window.clearTimeout(t); t = window.setTimeout(() => { refresh(); }, 400); }; + const offLogged = EventsOn('qso:logged', ping); const offUploaded = EventsOn('extsvc:uploaded', ping); const offDone = EventsOn('qslmgr:done', ping); const offEqsl = EventsOn('qsl:sent', ping); - return () => { offUploaded(); offDone(); offEqsl(); if (t) window.clearTimeout(t); }; + return () => { offLogged(); offUploaded(); offDone(); offEqsl(); if (t) window.clearTimeout(t); }; }, [refresh]); // The backend bulk-recomputed the materialised award_refs (an award definition