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.