From b7c87def5b5467dc0f387f47872b18d50095a7da Mon Sep 17 00:00:00 2001 From: rouggy Date: Wed, 2 Sep 2026 10:25:17 +0200 Subject: [PATCH] feat(decodes): say when the decoder's band is not the rig's MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A decoding application that loses its CAT link keeps announcing the last dial frequency it knew, and every decode after that carries a stale band. Nothing downstream can tell: the entity verdicts, the band filter and the FT map all believe it. Seen for real — MSHV kept saying 80 m, and Korea read as a NEW BAND because on 80 m it would have been. Said, not decided. Taking the rig's band instead would be wrong for anyone decoding a second receiver on another band, and a warning costs that setup nothing but a line to read past. Shown only while CAT is actually connected: an empty band means there is nothing to compare with, never that the rig is on no band. Opens 0.27.9. --- changelog.json | 10 +++++++ frontend/src/App.tsx | 3 +++ frontend/src/components/DecodesPanel.tsx | 34 ++++++++++++++++++++++-- frontend/src/lib/i18n.tsx | 4 +-- 4 files changed, 47 insertions(+), 4 deletions(-) diff --git a/changelog.json b/changelog.json index c437649..bbcf159 100644 --- a/changelog.json +++ b/changelog.json @@ -1,4 +1,14 @@ [ + { + "version": "0.27.9", + "date": "", + "en": [ + "FT decodes warn when the decoding application announces a band the radio is not on — the signature of a lost CAT link, where it repeats the last frequency it knew and every decode after that carries a stale band. Nothing downstream could tell, so NEW BAND was being judged against a band the operator had left. OpsLog says it rather than deciding: a second receiver on another band is a real setup, and it costs that one only a line to read past." + ], + "fr": [ + "Les FT decodes signalent quand le logiciel de décodage annonce une bande sur laquelle la radio n’est pas — la signature d’une liaison CAT perdue, où il répète la dernière fréquence connue et où tous les décodages suivants portent une bande périmée. Rien en aval ne pouvait s’en apercevoir : NOUVELLE BANDE était donc jugé sur une bande quittée. OpsLog le dit sans décider à votre place : un second récepteur sur une autre bande est une configuration légitime, et il ne lui en coûte qu’une ligne à ignorer." + ] + }, { "version": "0.27.8", "date": "", diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 02f238a..b2d8bb5 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -6328,6 +6328,9 @@ export default function App() { txState={txState} txStates={txStates} spotStatus={spotStatus as any} + // Only while CAT is actually connected: an empty band means "nothing to + // compare with", never "the rig is on no band". + rigBand={catState.connected ? (catState.band || '') : ''} myCall={station.callsign} // A click ANSWERS the station: it hands the decode back to WSJT-X/MSHV as // a Reply, which is the same thing as double-clicking the line in their diff --git a/frontend/src/components/DecodesPanel.tsx b/frontend/src/components/DecodesPanel.tsx index 3ff7873..be6d054 100644 --- a/frontend/src/components/DecodesPanel.tsx +++ b/frontend/src/components/DecodesPanel.tsx @@ -13,7 +13,7 @@ // come from the same resolver the cluster uses, so a call means the same thing in // both panels rather than being judged twice by two rules. import { useEffect, useMemo, useState } from 'react'; -import { Radio, Search, X, Signal, ArrowUpRight, Timer, Trash2, Ban, Columns2, Bot } from 'lucide-react'; +import { AlertTriangle, Radio, Search, X, Signal, ArrowUpRight, Timer, Trash2, Ban, Columns2, Bot } from 'lucide-react'; import { cn } from '@/lib/utils'; import { useI18n } from '@/lib/i18n'; import { chaseAllows } from '@/lib/spotDisplay'; @@ -95,6 +95,9 @@ interface Props { // receiver reported last, which is a coin toss — each pane needs its own. txStates?: Record; spotStatus: Record; + // The band the RIG is on, when CAT is connected. Only ever compared with what + // the decoder announces — see the drift warning. + rigBand?: string; onCall: (d: Decode) => void; myCall?: string; // Drop every decode and transmit message held for this panel. The list is a @@ -539,7 +542,7 @@ function buildPeriods(filtered: Decode[], txMsgs: TxMsg[]) { })); } -export function DecodesPanel({ decodes, txMsgs, txState, txStates, spotStatus, onCall, myCall, onClear, onHalt, autoCallOn, onToggleAutoCall }: Props) { +export function DecodesPanel({ decodes, txMsgs, txState, txStates, spotStatus, rigBand, onCall, myCall, onClear, onHalt, autoCallOn, onToggleAutoCall }: Props) { const { t } = useI18n(); // Column widths, dragged in the header and shared by every row. Persisted // through writeUiPref (not raw localStorage) so the layout travels with data/ @@ -597,6 +600,21 @@ export function DecodesPanel({ decodes, txMsgs, txState, txStates, spotStatus, o // The mode currently on the air, for the slot clock. The newest decode knows // best; between overs the transmit state still does. + // A decoder that has lost its CAT link keeps announcing the last dial + // frequency it knew, and every decode after that carries a stale band. Nothing + // downstream can tell: the entity verdicts, the band filter and the FT map all + // believe what the decoder said, and an operator ends up reading NEW BAND for a + // band they are not on. (Seen for real: MSHV lost CAT, kept saying 80 m, and + // Korea showed as a new band because on 80 m it would have been.) + // + // Said, not decided. Using the rig's band instead would be wrong for anyone + // decoding a second receiver on another band, and a warning costs that setup + // nothing but a line it can read past. + const decoderBand = decodes.length ? (decodes[decodes.length - 1].band ?? '') : ''; + const bandDrift = !!rigBand && !!decoderBand + && rigBand.toLowerCase() !== decoderBand.toLowerCase(); + const driftInstance = decodes.length ? (decodes[decodes.length - 1].instance ?? '') : ''; + const liveMode = decodes.length ? decodes[decodes.length - 1].mode : txState?.mode; const liveTr = trSeconds(liveMode, decodes.length ? decodes[decodes.length - 1].tr_period : undefined); @@ -733,6 +751,18 @@ export function DecodesPanel({ decodes, txMsgs, txState, txStates, spotStatus, o what the transmit state reports, so it is right the moment anything is heard and keeps running when the band goes quiet. */} + {bandDrift && ( + + + {t('dec.bandDrift', { + app: driftInstance || t('dec.bandDriftApp'), + dec: decoderBand.toUpperCase(), + rig: (rigBand ?? '').toUpperCase(), + })} + + )}