feat(cluster): refresh spot statuses after a log — visibility-gated & debounced

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.
This commit is contained in:
2026-08-06 01:13:22 +02:00
parent 57a9bed145
commit fb1af9b6a2
2 changed files with 32 additions and 2 deletions
+28
View File
@@ -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.