From fb1af9b6a279991d0222650402bbbe600775d994 Mon Sep 17 00:00:00 2001 From: Gregory Salaun Date: Thu, 6 Aug 2026 01:13:22 +0200 Subject: [PATCH] =?UTF-8?q?feat(cluster):=20refresh=20spot=20statuses=20af?= =?UTF-8?q?ter=20a=20log=20=E2=80=94=20visibility-gated=20&=20debounced?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A just-worked call/entity/POTA/prefix/slot kept its NEW badge because per-spot status is cached and never expires. Drop the cache on qso:logged so the grid re-evaluates. Kept cheap for old machines / big logbooks: only fires while the cluster is on screen (a log while hidden marks it dirty and refreshes once on next open), and debounced 2 s so a fast run coalesces instead of re-scanning the logbook per QSO. --- changelog.json | 6 ++++-- frontend/src/App.tsx | 28 ++++++++++++++++++++++++++++ 2 files changed, 32 insertions(+), 2 deletions(-) diff --git a/changelog.json b/changelog.json index d71f42e..36ac1a9 100644 --- a/changelog.json +++ b/changelog.json @@ -5,12 +5,14 @@ "en": [ "QSO edit: added the QRZ ↗ button next to the callsign, like the main entry form — one click opens that station's qrz.com profile.", "PowerGenius XL: the fan mode (Standard / Contest / Broadcast) changes on the amp again. A previous fix dropped the \"setup\" prefix the amp requires (\"setup fanmode=…\"), so the amp rejected the command and the mode snapped straight back. The correct command is restored.", - "Performance: fixed a slowdown introduced in 0.23.6. To dim the \"represents nothing\" spots, the DX Cluster grid was redrawing EVERY row on each spot-status update, which pegged the CPU on a busy cluster (some PCs became sluggish). The dimming now updates with a light per-cell refresh instead — same look, no more churn." + "Performance: fixed a slowdown introduced in 0.23.6. To dim the \"represents nothing\" spots, the DX Cluster grid was redrawing EVERY row on each spot-status update, which pegged the CPU on a busy cluster (some PCs became sluggish). The dimming now updates with a light per-cell refresh instead — same look, no more churn.", + "DX Cluster: after you log a QSO, the spots update — a callsign, entity, POTA, prefix or band/mode slot you just worked stops showing its NEW badge, instead of staying \"new\" until a restart. Kept fast: it re-evaluates only while the cluster is actually on screen (nothing runs otherwise — a QSO logged while it's hidden refreshes once when you next open it), and it's debounced so a quick run doesn't re-scan on every QSO." ], "fr": [ "Édition de QSO : ajout du bouton QRZ ↗ à côté de l'indicatif, comme dans le formulaire de saisie — un clic ouvre le profil qrz.com de la station.", "PowerGenius XL : le mode ventilateur (Standard / Contest / Broadcast) change de nouveau sur l'ampli. Un correctif précédent avait retiré le préfixe « setup » exigé par l'ampli (« setup fanmode=… »), qui rejetait donc la commande et le mode revenait aussitôt en arrière. La bonne commande est rétablie.", - "Performance : correction d'un ralentissement apparu en 0.23.6. Pour atténuer les spots « qui ne représentent rien », la grille du DX Cluster redessinait TOUTES les lignes à chaque mise à jour de statut, ce qui saturait le CPU sur un cluster actif (des PC devenaient lents). L'atténuation se met désormais à jour via un rafraîchissement léger par cellule — même rendu, sans le brassage." + "Performance : correction d'un ralentissement apparu en 0.23.6. Pour atténuer les spots « qui ne représentent rien », la grille du DX Cluster redessinait TOUTES les lignes à chaque mise à jour de statut, ce qui saturait le CPU sur un cluster actif (des PC devenaient lents). L'atténuation se met désormais à jour via un rafraîchissement léger par cellule — même rendu, sans le brassage.", + "DX Cluster : après avoir loggué un QSO, les spots se mettent à jour — un indicatif, une entité, un POTA, un préfixe ou un slot bande/mode que vous venez de contacter cesse d'afficher son badge NEW, au lieu de rester « new » jusqu'au redémarrage. Reste rapide : la réévaluation n'a lieu que si le cluster est affiché (sinon rien ne tourne — un QSO loggué cluster caché se rafraîchit une fois à sa réouverture), et c'est débouncé pour ne pas re-scanner à chaque QSO en série." ] }, { diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 02fb6fa..d4991b3 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -1500,6 +1500,34 @@ export default function App() { // still need resolving without re-subscribing the cluster:spot listener. const spotStatusRef = useRef(spotStatus); useEffect(() => { spotStatusRef.current = spotStatus; }, [spotStatus]); + // After a QSO is logged, the call/entity/POTA/prefix/slot just worked is no + // longer "new" — but each spot's status is cached and never expires, so the + // cluster kept showing the old NEW badge until a restart. Dropping the cache + // re-evaluates every visible spot against the fresh logbook (the poll refetches + // the now-unknown statuses). Kept CHEAP for old machines / big logbooks: + // • only when the cluster is actually on screen — otherwise it costs nothing; + // • a log made while it's hidden just marks it dirty and refetches ONCE the + // next time you open the cluster; + // • debounced, so a fast run coalesces instead of re-scanning per QSO. + const clusterVisibleRef = useRef(false); + const spotsDirtyRef = useRef(false); + useEffect(() => { + const vis = mainPaneLeft === 'cluster' || mainPaneRight === 'cluster' || activeTab === 'cluster'; + if (vis && !clusterVisibleRef.current && spotsDirtyRef.current) { + spotsDirtyRef.current = false; + setSpotStatus({}); + } + clusterVisibleRef.current = vis; + }, [mainPaneLeft, mainPaneRight, activeTab]); + useEffect(() => { + let t: number | undefined; + const off = EventsOn('qso:logged', () => { + if (!clusterVisibleRef.current) { spotsDirtyRef.current = true; return; } + if (t) window.clearTimeout(t); + t = window.setTimeout(() => setSpotStatus({}), 2000); + }); + return () => { off(); if (t) window.clearTimeout(t); }; + }, []); // Incoming spots are staged here for a few ms, their status resolved, then // committed to `spots` together — so a row paints with its NEW BAND/MODE badge // already on, instead of flashing plain text then flipping to the pill.