fix(cat): the chip says what YOU called the radio

It showed what the radio calls itself over CAT — 'Kenwood (911)' for a K3,
a bare 'CAT' for a Flex that reports nothing useful. That is the answer to
a question nobody asks once the rig has a name in the list, and it made
the two radios look alike in the one place they have to be told apart.

The name the operator typed comes first now, on the chip and in the menu,
and falls back to the radio's own identity when they typed none. The model
identity is still in the tooltip, where it answers 'what is actually
connected' without taking the place of 'which of my rigs is this'.
This commit is contained in:
2026-08-27 00:42:26 +02:00
parent cf5efea3e4
commit 2594697e00
4 changed files with 25 additions and 8 deletions
+8 -1
View File
@@ -118,9 +118,16 @@ func (a *App) ActiveRadioID() string {
} }
// RadioListEntry is what the status-bar menu needs: enough to draw a row. // RadioListEntry is what the status-bar menu needs: enough to draw a row.
//
// Name is what the OPERATOR typed, empty when they typed nothing; Label is what
// to draw when there is no better idea. The two are separate because the caller
// has a better idea than we do: the status bar knows what the radio calls
// itself over CAT, and "FTDX10" beats "Radio 2" — but only where the operator
// has not given it a name of their own, which beats both.
type RadioListEntry struct { type RadioListEntry struct {
ID string `json:"id"` ID string `json:"id"`
Name string `json:"name"` Name string `json:"name"`
Label string `json:"label"`
Backend string `json:"backend"` Backend string `json:"backend"`
Active bool `json:"active"` Active bool `json:"active"`
} }
@@ -135,7 +142,7 @@ func (a *App) ListRadios() []RadioListEntry {
out := make([]RadioListEntry, 0, len(list)) out := make([]RadioListEntry, 0, len(list))
for i, r := range list { for i, r := range list {
out = append(out, RadioListEntry{ out = append(out, RadioListEntry{
ID: r.ID, Name: radioLabel(r, i), ID: r.ID, Name: strings.TrimSpace(r.Name), Label: radioLabel(r, i),
Backend: r.Settings.Backend, Active: r.ID == active, Backend: r.Settings.Backend, Active: r.ID == active,
}) })
} }
+14 -6
View File
@@ -313,9 +313,17 @@ function RadioChip({ catUp, catState, onOpenSettings }: {
return () => document.removeEventListener('mousedown', onDoc); return () => document.removeEventListener('mousedown', onDoc);
}, [open]); }, [open]);
// What the operator called this radio comes first.
//
// The chip showed what the RADIO calls itself over CAT — "Kenwood (911)" for
// a K3, a bare "CAT" for a Flex that reports nothing useful — which is the
// answer to a question nobody asked once the rig has a name in the list. The
// model identity is still there, in the tooltip.
const active = radios.find((r: any) => r.active);
const named = (active?.name || '').trim();
const label = catUp const label = catUp
? (catState.rig || 'CAT') ? (named || catState.rig || 'CAT')
: (catState.enabled ? (shortCatError(catState.error) || 'CAT off') : 'CAT'); : (catState.enabled ? (named || shortCatError(catState.error) || 'CAT off') : (named || 'CAT'));
// The menu opens with ONE radio too. // The menu opens with ONE radio too.
// //
// It was only shown from two, on the reasoning that a single radio has // It was only shown from two, on the reasoning that a single radio has
@@ -330,9 +338,9 @@ function RadioChip({ catUp, catState, onOpenSettings }: {
<button <button
type="button" type="button"
onClick={() => { if (!has) { onOpenSettings(); return; } load(); setOpen((o) => !o); }} onClick={() => { if (!has) { onOpenSettings(); return; } load(); setOpen((o) => !o); }}
title={has title={catUp
? 'Radios — right-click for the CAT settings' ? `${catState.rig || catState.backend || 'connected'}${has ? ' — click to switch radio, right-click for the CAT settings' : ''}`
: (catUp ? `CAT: ${catState.rig || catState.backend || 'connected'}` : (catState.error || 'CAT'))} : (catState.error || 'CAT')}
onContextMenu={(e) => { e.preventDefault(); onOpenSettings(); }} onContextMenu={(e) => { e.preventDefault(); onOpenSettings(); }}
className={cn('inline-flex items-center gap-1.5 h-5 px-2 rounded-full border text-[11px] transition-colors', className={cn('inline-flex items-center gap-1.5 h-5 px-2 rounded-full border text-[11px] transition-colors',
'border-border hover:bg-muted cursor-pointer')} 'border-border hover:bg-muted cursor-pointer')}
@@ -356,7 +364,7 @@ function RadioChip({ catUp, catState, onOpenSettings }: {
r.active && 'font-semibold text-primary')} r.active && 'font-semibold text-primary')}
> >
<span className={cn('size-1.5 rounded-full shrink-0', r.active ? 'bg-success' : 'bg-muted-foreground/30')} /> <span className={cn('size-1.5 rounded-full shrink-0', r.active ? 'bg-success' : 'bg-muted-foreground/30')} />
<span className="flex-1 truncate">{r.name}</span> <span className="flex-1 truncate">{(r.name || '').trim() || r.label}</span>
<span className="text-[10px] text-muted-foreground uppercase">{r.backend}</span> <span className="text-[10px] text-muted-foreground uppercase">{r.backend}</span>
</button> </button>
))} ))}
+1 -1
View File
@@ -3224,7 +3224,7 @@ function SettingsModalImpl({ onClose, onSaved, initialSection, onMainPaneChanged
<SelectTrigger className="h-9 flex-1"><SelectValue placeholder={t('cat.radio')} /></SelectTrigger> <SelectTrigger className="h-9 flex-1"><SelectValue placeholder={t('cat.radio')} /></SelectTrigger>
<SelectContent> <SelectContent>
{radios.map((r: any, i: number) => ( {radios.map((r: any, i: number) => (
<SelectItem key={r.id} value={r.id}>{r.name?.trim() || `Radio ${i + 1}`}</SelectItem> <SelectItem key={r.id} value={r.id}>{r.name?.trim() || r.label || `Radio ${i + 1}`}</SelectItem>
))} ))}
</SelectContent> </SelectContent>
</Select> </Select>
+2
View File
@@ -3606,6 +3606,7 @@ export namespace main {
export class RadioListEntry { export class RadioListEntry {
id: string; id: string;
name: string; name: string;
label: string;
backend: string; backend: string;
active: boolean; active: boolean;
@@ -3617,6 +3618,7 @@ export namespace main {
if ('string' === typeof source) source = JSON.parse(source); if ('string' === typeof source) source = JSON.parse(source);
this.id = source["id"]; this.id = source["id"];
this.name = source["name"]; this.name = source["name"];
this.label = source["label"];
this.backend = source["backend"]; this.backend = source["backend"];
this.active = source["active"]; this.active = source["active"];
} }