feat(cluster): the spot list size is a setting

The list is a ring buffer that held a thousand spots, and on a busy
evening a thousand arrive in a couple of minutes. So the buffer decided
how long a spot lived, and the spot LIFETIME setting never got the chance
to expire anything: fifteen minutes meant nothing when the oldest spot
was pushed out after two.

Now settable (Preferences → Cluster, beside the lifetime, since between
them they decide the same thing), 100 to 10 000. The ceiling is a real
limit rather than a round number: every spot is matched against the
worked index and the alert rules, and is a row the cluster grid and every
open band map re-render.
This commit is contained in:
2026-08-24 19:15:32 +02:00
parent 4e9b1eebe9
commit 27d0952d01
7 changed files with 103 additions and 9 deletions
+25 -1
View File
@@ -58,7 +58,7 @@ import {
GetFolderSync, SaveFolderSync, PickFolderSyncFolder, GetFolderSyncStatus, SyncFolderNow,
GetRelayAuto, SaveRelayAuto, GetStationDevices,
GetAwardDefs, GetTrackedAwards, SaveTrackedAwards,
GetBandOpenSettings, SaveBandOpenSettings, GetGridScopeSettings, SaveGridScopeSettings, GetPSKReporterStatus, GetChaseNewGrids, SetChaseNewGrids, GetChaseNew, SetChaseNew, GetGridCacheStatus, GetLinkedAmps, SetLinkedAmps, GetSpotTTLMinutes, SetSpotTTLMinutes,
GetBandOpenSettings, SaveBandOpenSettings, GetGridScopeSettings, SaveGridScopeSettings, GetPSKReporterStatus, GetChaseNewGrids, SetChaseNewGrids, GetChaseNew, SetChaseNew, GetGridCacheStatus, GetLinkedAmps, SetLinkedAmps, GetSpotTTLMinutes, SetSpotTTLMinutes, GetSpotMax, SetSpotMax,
} from '../../wailsjs/go/main/App';
import type { profile as profileModels } from '../../wailsjs/go/models';
import type { LookupSettingsForm, StationSettingsForm, ListsSettingsForm, ModePresetForm } from '@/types';
@@ -1942,6 +1942,7 @@ export function SettingsModal({ onClose, onSaved, initialSection, onMainPaneChan
const [chaseNew, setChaseNew] = useState(false);
const [spotTTL, setSpotTTL] = useState(0);
const [spotTTLText, setSpotTTLText] = useState('0');
const [spotMaxText, setSpotMaxText] = useState('1000');
const [gridStat, setGridStat] = useState<any>(null);
const [pskrStatus, setPskrStatus] = useState<any>(null);
const saveBandOpen = async (next: any) => {
@@ -1955,6 +1956,7 @@ export function SettingsModal({ onClose, onSaved, initialSection, onMainPaneChan
try { setChaseNew(await GetChaseNew()); } catch { /* defaults stand */ }
try { setLinkedAmps((await GetLinkedAmps()) ?? []); } catch { /* defaults stand */ }
try { const n = await GetSpotTTLMinutes(); setSpotTTL(n); setSpotTTLText(String(n)); } catch { /* defaults stand */ }
try { const n = await GetSpotMax(); setSpotMaxText(String(n)); } catch { /* defaults stand */ }
})();
// Poll the feed while the panel is open: a live count is the only thing that
// distinguishes "connected" from "connected and receiving nothing".
@@ -4973,6 +4975,28 @@ export function SettingsModal({ onClose, onSaved, initialSection, onMainPaneChan
}} />
<span className="text-xs text-muted-foreground">{t('clu.spotTtlHint')}</span>
</div>
{/* The list size, beside the lifetime because between them they decide
the same thing: whichever runs out first removes the spot. */}
<div className="flex items-center gap-3 flex-wrap">
<span className="text-sm">{t('clu.spotMax')}</span>
<div className="inline-flex rounded-md border border-border overflow-hidden text-xs">
{[500, 1000, 2500, 5000].map((v) => (
<button key={v} type="button"
onClick={() => { setSpotMaxText(String(v)); SetSpotMax(v).catch(() => {}); }}
className={cn('px-2.5 py-1.5 font-medium', Number(spotMaxText) === v ? 'bg-primary text-primary-foreground' : 'text-muted-foreground hover:bg-muted')}>
{v}
</button>
))}
</div>
<Input className="h-8 w-24" value={spotMaxText}
onChange={(e) => {
const raw = e.target.value.replace(/[^0-9]/g, '');
setSpotMaxText(raw);
const n = parseInt(raw, 10);
if (Number.isFinite(n) && n > 0) SetSpotMax(Math.min(10000, n)).catch(() => {});
}} />
<span className="text-xs text-muted-foreground">{t('clu.spotMaxHint')}</span>
</div>
</div>
<div className="border-t border-border/60 pt-3 space-y-2">