fix(watchlist widget): one line per spot

Callsign, band, mode, badge, age in reading order, and the widget widened
to 290 px so the badge and the age never squeeze the callsign. Two lines
made the panel twice as tall for the same three facts; the entry that
matched — which only differs when it is a prefix — moved into the row's
tooltip.
This commit is contained in:
2026-09-06 00:10:21 +02:00
parent 03e71bfdf2
commit 4321eae647
3 changed files with 29 additions and 34 deletions
+1 -1
View File
@@ -7655,7 +7655,7 @@ export default function App() {
width. The classic dial sizes itself from the inside and expects a fixed
column; the current one asks for the width it needs. */}
{showWatchWidget && (
<div className="w-[260px] shrink-0 min-h-0" style={{ order: wOrder('watchlist') }}>
<div className="w-[290px] shrink-0 min-h-0" style={{ order: wOrder('watchlist') }}>
{/* Same handler as a cluster row, for the same reason as Chase
new: a row here IS a spot, and half the reflex the call
without the frequency, or the frequency without the mode is
+26 -31
View File
@@ -110,39 +110,34 @@ export function WatchlistWidget({ spots, spotStatus, onPick, onClose }: Props) {
key={`${e.callsign}-${s.dx_call}-${s.band}-${mode}-${i}`}
type="button"
onClick={() => onPick?.(s)}
title={t('wlw.pick', { call: s.dx_call })}
className="w-full text-left px-2 py-1 border-b border-border/20 hover:bg-muted/50 transition-colors"
title={e.callsign === s.dx_call
? t('wlw.pick', { call: s.dx_call })
: `${t('wlw.pick', { call: s.dx_call })} — ${e.callsign}`}
className="w-full text-left px-2 py-1 border-b border-border/20 hover:bg-muted/50 transition-colors flex items-center gap-1.5"
>
<div className="flex items-center gap-1.5">
<span className="font-mono text-[13px] font-bold text-warning truncate">{s.dx_call}</span>
{/* The entry it matched, when it is a prefix: "DP1*" told the
operator to watch a fleet, and which member turned up is not
obvious from the callsign alone. */}
{e.callsign !== s.dx_call && (
<span className="text-[9px] text-muted-foreground shrink-0">{e.callsign}</span>
)}
<div className="flex-1" />
<span className="font-mono text-[10px] text-muted-foreground/70 tabular-nums shrink-0">
{age(s)}
{/* One line, in reading order: who, where, what it is worth, how
long ago. The callsign is the only part allowed to give way —
everything else is short and fixed, and a row that wraps is a
row an operator has to parse instead of scan. */}
<span className="font-mono text-[13px] font-bold text-warning truncate">{s.dx_call}</span>
{s.band && (
<span className="rounded px-1 py-px text-[10px] font-bold uppercase bg-info-muted text-info-muted-foreground shrink-0">
{s.band}
</span>
</div>
<div className="flex items-center gap-1.5 mt-0.5">
{s.band && (
<span className="rounded px-1 py-px text-[10px] font-bold uppercase bg-info-muted text-info-muted-foreground shrink-0">
{s.band}
</span>
)}
{mode && <span className="font-mono text-[10px] text-muted-foreground shrink-0">{mode}</span>}
<div className="flex-1" />
{/* Why it is worth interrupting a QSO for — or not. */}
{badge && (
<span className="rounded px-1 py-px text-[10px] font-bold uppercase tracking-wide border shrink-0"
style={{ color: badge.colour, borderColor: `color-mix(in srgb, ${badge.colour} 45%, transparent)`,
background: `color-mix(in srgb, ${badge.colour} 12%, transparent)` }}>
{t(badge.key)}
</span>
)}
</div>
)}
{mode && <span className="font-mono text-[10px] text-muted-foreground shrink-0">{mode}</span>}
<div className="flex-1" />
{/* Why it is worth interrupting a QSO for — or not. */}
{badge && (
<span className="rounded px-1 py-px text-[10px] font-bold uppercase tracking-wide border shrink-0"
style={{ color: badge.colour, borderColor: `color-mix(in srgb, ${badge.colour} 45%, transparent)`,
background: `color-mix(in srgb, ${badge.colour} 12%, transparent)` }}>
{t(badge.key)}
</span>
)}
<span className="font-mono text-[10px] text-muted-foreground/70 tabular-nums shrink-0 w-7 text-right">
{age(s)}
</span>
</button>
);
})}