fix(cluster): stop saying "waiting for spots" when 76 have arrived
An operator sent two screenshots: 76 LIVE in the counter, and the panel beside it reading "Waiting for spots… Spots will appear as the cluster sends them." Both his band and mode were locked to the rig — 20 m, SSB — so the filters were doing exactly their job and the empty state was describing a different problem entirely. He went looking for a connection fault. It now says how many arrived, names every filter currently narrowing the list, and offers one button to clear them all. The band and mode locks are named first: they follow the rig rather than a click, so they are the two nobody remembers switching on. Also times the connection. He reports the first launch taking a while to produce spots and a restart connecting instantly, which is the signature of a slow name resolution rather than a slow node — the OS caches the answer, so the second run skips it. The log now carries the dial duration and the delay to the first spot, which separates that from a node that simply had nothing to say. Hypothesis, not conclusion: the next log settles it.
This commit is contained in:
+64
-2
@@ -1539,6 +1539,41 @@ export default function App() {
|
||||
// Hide spots already worked (exact call worked, or this band+mode slot done).
|
||||
const [clusterHideWorked, setClusterHideWorked] = useState(() => lsBool('opslog.clusterHideWorked', false));
|
||||
|
||||
// Everything currently narrowing the spot list, in words. Shown when spots
|
||||
// arrived and none survived — the operator needs to know WHICH filter to
|
||||
// loosen, and the two locks are the least memorable because they follow the
|
||||
// rig rather than a click.
|
||||
const clusterActiveFilterSummary = useMemo(() => {
|
||||
const on: string[] = [];
|
||||
if (clusterLockBand) on.push(t('clg2.fBandLock'));
|
||||
else if (clusterBands.size > 0) on.push(t('clg2.fBands', { list: [...clusterBands].join(', ') }));
|
||||
if (clusterLockMode) on.push(t('clg2.fModeLock'));
|
||||
else if (clusterModeFilter.size > 0) on.push(t('clg2.fModes', { list: [...clusterModeFilter].join(', ') }));
|
||||
if (clusterStatusFilter.size > 0) on.push(t('clg2.fStatus'));
|
||||
if (clusterHideWorked) on.push(t('clg2.fHideWorked'));
|
||||
if (clusterLotwOnly) on.push(t('clg2.fLotwOnly'));
|
||||
if (clusterSpotterConts.size > 0) on.push(t('clg2.fSpotterCont'));
|
||||
if (clusterFilterSource) on.push(t('clg2.fSource'));
|
||||
if (clusterSearch.trim()) on.push(t('clg2.fSearch', { q: clusterSearch.trim() }));
|
||||
return on.join(' · ');
|
||||
}, [t, clusterLockBand, clusterBands, clusterLockMode, clusterModeFilter, clusterStatusFilter,
|
||||
clusterHideWorked, clusterLotwOnly, clusterSpotterConts, clusterFilterSource, clusterSearch]);
|
||||
|
||||
// Undo every one of them at once. A list of ten switches spread down a panel
|
||||
// is not something to walk back by hand when the answer is "show me anything".
|
||||
const clearClusterFilters = useCallback(() => {
|
||||
setClusterLockBand(false);
|
||||
setClusterBands(new Set());
|
||||
setClusterLockMode(false);
|
||||
setClusterModeFilter(new Set());
|
||||
setClusterStatusFilter(new Set());
|
||||
setClusterHideWorked(false);
|
||||
setClusterLotwOnly(false);
|
||||
setClusterSpotterConts(new Set());
|
||||
setClusterFilterSource('');
|
||||
setClusterSearch('');
|
||||
}, []);
|
||||
|
||||
// Persist every cluster filter selection whenever it changes, so it is still
|
||||
// set after a close/reopen.
|
||||
useEffect(() => {
|
||||
@@ -6738,14 +6773,41 @@ export default function App() {
|
||||
// pane). All the filter state lives in the right-side panel.
|
||||
const rendered = clusterRenderedRows;
|
||||
if (rendered.length === 0) {
|
||||
const connected = clusterServerStatuses.some((s) => s.state === 'connected');
|
||||
// Spots HAVE arrived and the filters ate every one of them.
|
||||
//
|
||||
// This used to say "Waiting for spots…" regardless, which is a
|
||||
// lie the moment the counter beside it reads 76 live: an
|
||||
// operator reads the two together and goes looking for a
|
||||
// connection fault instead of at the filter panel. The band and
|
||||
// mode locks are the usual culprits and the least visible —
|
||||
// they follow the rig, so nobody remembers switching them on.
|
||||
if (connected && spots.length > 0) {
|
||||
return (
|
||||
<div className="flex-1 flex flex-col items-center justify-center text-muted-foreground gap-2 py-12 px-6 text-center">
|
||||
<SlidersHorizontal className="size-10 opacity-30" />
|
||||
<div className="text-sm font-semibold text-foreground/70">
|
||||
{t('clg2.allFiltered', { n: spots.length })}
|
||||
</div>
|
||||
{clusterActiveFilterSummary && (
|
||||
<div className="text-xs max-w-md leading-relaxed">
|
||||
{t('clg2.activeFilters')} <span className="font-medium text-foreground/80">{clusterActiveFilterSummary}</span>
|
||||
</div>
|
||||
)}
|
||||
<Button size="sm" variant="outline" className="mt-1" onClick={clearClusterFilters}>
|
||||
{t('clg2.clearAllFilters')}
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<div className="flex-1 flex flex-col items-center justify-center text-muted-foreground gap-2 py-12">
|
||||
<Hash className="size-10 opacity-30" />
|
||||
<div className="text-sm font-semibold text-foreground/70">
|
||||
{clusterServerStatuses.some((s) => s.state === 'connected') ? 'Waiting for spots…' : 'No active connection'}
|
||||
{connected ? 'Waiting for spots…' : 'No active connection'}
|
||||
</div>
|
||||
<div className="text-xs">
|
||||
{clusterServerStatuses.some((s) => s.state === 'connected')
|
||||
{connected
|
||||
? 'Spots will appear as the cluster sends them.'
|
||||
: 'Use Connect all (or configure a cluster in Settings → DX Cluster).'}
|
||||
</div>
|
||||
|
||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user