fix(watchlist): read the cluster's slot index, and cap the card width
The first version asked the alerts' worked-index — keyed by RAW mode (FT8) — with a CLASS name (DIGI). Nothing ever matched, and a DXpedition worked on five bands showed every slot as Needed. The verdict now reads the cluster's own WorkedCallSlotKeys set, built in the cached status maps (now unconditionally — it used to exist only when a display option wanted it). Same index, same mode normalisation as the grid, so the watchlist can never contradict the cluster about the same spot. The contest today-set is normalised through the same function. And the cards column is capped at a reading width instead of stretching callsign-to-badge lines across the whole window.
This commit is contained in:
@@ -19803,9 +19803,10 @@ func (a *App) clusterStatusMaps() *clusterStatusCache {
|
||||
// "Already worked only on the same slot" option (Settings → DX Cluster): the
|
||||
// WORKED-call flag then needs the SAME band and mode (digital-grouped through
|
||||
// the same normMode when that option is on) rather than the call anywhere.
|
||||
if sameSlot || slotHighlight {
|
||||
// Built UNCONDITIONALLY since the watchlist arrived: its per-slot verdict
|
||||
// needs this set whatever the cluster's own display options say, and the
|
||||
// scan already runs at cache-rebuild cadence, not per batch.
|
||||
c.workedCallSlots, _ = a.qso.WorkedCallSlotKeys(a.ctx, c.normMode)
|
||||
}
|
||||
// Orthogonal dimensions: worked US counties (for the ULS callsign→county
|
||||
// lookup) and worked POTA parks.
|
||||
c.workedCounties, _ = a.qso.WorkedCountyKeys(a.ctx, award.USCountyKey)
|
||||
|
||||
+31
-12
@@ -11,7 +11,6 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"hamlog/internal/qso"
|
||||
"hamlog/internal/watchlist"
|
||||
|
||||
wruntime "github.com/wailsapp/wails/v2/pkg/runtime"
|
||||
@@ -68,18 +67,38 @@ type WatchlistSlotQuery struct {
|
||||
|
||||
// WatchlistWorkedSlots answers a batch of slot questions for the tab.
|
||||
//
|
||||
// One pass over today's contacts and the in-memory worked index rather than a
|
||||
// query per spot: the tab refreshes on every spot burst, and a busy evening
|
||||
// must not turn into a query storm. Contest entries read the TODAY set — the
|
||||
// midnight-UTC reset is the query's date bound, nothing stored, nothing to
|
||||
// reset. Mode is compared at CLASS grain (FT8 and FT4 are both Digital),
|
||||
// matching how the cluster's own worked_slot judges a slot.
|
||||
// Normal entries read the CLUSTER's own slot set — the same cached
|
||||
// WorkedCallSlotKeys index that colours the grid — so the watchlist can never
|
||||
// contradict the cluster about the same spot. That index normalises the mode
|
||||
// exactly as the operator configured (digital grouping on → FT4 counts as FT8's
|
||||
// class; off → exact mode), and the first version of this ignored that: it
|
||||
// asked the alerts' raw-mode index with a CLASS name, matched nothing, and
|
||||
// showed a fully-worked DXpedition as all Needed.
|
||||
//
|
||||
// Contest entries read TODAY's contacts instead — the midnight-UTC reset is
|
||||
// the query's date bound, nothing stored, nothing to reset — normalised through
|
||||
// the same function so the two answers use one grammar.
|
||||
func (a *App) WatchlistWorkedSlots(queries []WatchlistSlotQuery) []bool {
|
||||
out := make([]bool, len(queries))
|
||||
if a.qso == nil || len(queries) == 0 {
|
||||
return out
|
||||
}
|
||||
// Today's slots, only if some entry needs them.
|
||||
idx := a.clusterStatusMaps()
|
||||
norm := func(m string) string {
|
||||
m = strings.ToUpper(strings.TrimSpace(m))
|
||||
if idx.normMode != nil {
|
||||
m = idx.normMode(m)
|
||||
}
|
||||
return m
|
||||
}
|
||||
slotKey := func(call, band, mode string) string {
|
||||
up := strings.ToUpper(strings.TrimSpace(call))
|
||||
b := strings.ToLower(strings.TrimSpace(band))
|
||||
if m := norm(mode); m != "" {
|
||||
return up + "|" + b + "|" + m
|
||||
}
|
||||
return up + "|" + b
|
||||
}
|
||||
needToday := false
|
||||
for _, q := range queries {
|
||||
if q.Contest {
|
||||
@@ -93,16 +112,16 @@ func (a *App) WatchlistWorkedSlots(queries []WatchlistSlotQuery) []bool {
|
||||
rows, err := a.qso.SlotsSince(a.ctx, midnight)
|
||||
if err == nil {
|
||||
for _, r := range rows {
|
||||
today[wcbmKey(r.Callsign, r.Band, qso.ModeClass(r.Mode))] = true
|
||||
today[slotKey(r.Callsign, r.Band, r.Mode)] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
for i, q := range queries {
|
||||
key := wcbmKey(q.Call, q.Band, qso.ModeClass(q.Mode))
|
||||
key := slotKey(q.Call, q.Band, q.Mode)
|
||||
if q.Contest {
|
||||
out[i] = today[key]
|
||||
} else {
|
||||
out[i] = a.isWorkedBandMode(q.Call, q.Band, qso.ModeClass(q.Mode))
|
||||
} else if idx.workedCallSlots != nil {
|
||||
_, out[i] = idx.workedCallSlots[key]
|
||||
}
|
||||
}
|
||||
return out
|
||||
|
||||
@@ -167,7 +167,9 @@ export function WatchlistTab({ spots, spotStatus, onSpotSelect, onSpotClick }: P
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="flex flex-col min-h-0 flex-1 gap-2 p-2">
|
||||
// Capped and centred: a card is a reading surface, and callsign-to-badge
|
||||
// lines stretched across a 34-inch window are not readable, they are long.
|
||||
<div className="flex flex-col min-h-0 flex-1 gap-2 p-2 w-full max-w-4xl mx-auto">
|
||||
{/* toolbar */}
|
||||
<div className="flex items-center gap-2 flex-wrap">
|
||||
<Eye className="size-4 text-primary shrink-0" />
|
||||
|
||||
Reference in New Issue
Block a user