diff --git a/changelog.json b/changelog.json index 59a69d2..f52ce65 100644 --- a/changelog.json +++ b/changelog.json @@ -1,4 +1,66 @@ [ + { + "version": "0.27.13", + "date": "", + "en": [ + "Chase new: the panel now says what the FEED is doing — up or down, how many reports it has taken and how many receiver squares it is subscribed to. An empty list said nothing about whether anything was arriving at all, which is the first question when it looks dead.", + "Chase new: the count in the header counted what SURVIVED the category filters, so a panel holding twelve stations with the filters off read “0 heard”. It now shows both — “3 of 12 heard” — and the footer takes the radius from the feed instead of claiming ~300 km whatever the setting says.", + "Chase new: category filters that hide everything are no longer remembered as a preference. A stored set holding none of the categories on offer — what a preference written by an older build becomes once a category is renamed — hid the whole panel at every launch with nothing to say why.", + "Chase new: a band selection of its own, under the option (160 m to 70 cm). The station’s band list still applies underneath — this one says what is worth WATCHING tonight, which is a different question: a station equipped for 2 m and 70 cm that is chasing HF no longer has them in the list. A band switched off is dropped as it arrives, so it stops taking a place.", + "Auto-call cut your own 73 in half. When the DX’s RRR was decoded the QSO read as finished and the next station was picked in the same instant — but a reply is not queued: WSJT-X acts on it at once, drops the exchange and starts calling the new station. Nothing is called over a transmission in progress now, and the four-minute backstop restarts when the station begins answering, so a QSO that started after a long wait cannot be halted mid-exchange.", + "Auto-call: a real need now outranks an unconfirmed one at the same level. Hunting “new + unconfirmed”, a band already worked and awaiting a QSL is flagged as needed too — rightly — but it is not the same catch as a band never worked, and the two ranked identically: with one of each in a period the engine took whichever was louder. An unconfirmed need drops below the real one at its own level and no further: an unconfirmed new band still outranks a real new slot.", + "PSK Reporter panel: the history query now runs in BOTH feed scopes and is asked again every five minutes while a station is watched. It ran once, and only on the narrow feed — so a target picked shortly after opening the panel showed one decode where another program, subscribed for an hour, showed four. Uploaders batch their reports about every five minutes, which is the cadence this now matches.", + "PSK Reporter panel: the texts said “in the last 5 min” while the window had become ten. The window is what changed; the sentences had not.", + "Cluster: “superfox”, “super fox”, “fox/hound” and “F/H” in a spot comment are read as FT8. They are WSJT-X’s DXpedition transmit modes, not modes of their own, and the comment fell through to the band plan and came out DATA — which then decided the band+mode verdict, so a ZD8 on 21.071 read as a new DATA slot rather than the new FT8 one it is.", + "FT decodes: a receiver column appears when more than one decoder feeds one merged list. Two decoders on the same band send the same stations twice with their own SNR, and nothing on the row said which was which.", + "Auto-call answered four to six seconds late, so the decoder started its call well inside the slot. A period was judged a whole slot plus four seconds after the DECODE’S OWN TIMESTAMP — which is the start of that slot, thirteen seconds before its decodes exist. It is now judged 0.8 s after the last decode ARRIVES, and the sweeper ticks four times a second instead of once every two: the answer leaves in the same second the decodes landed, which is what the next slot boundary requires.", + "FT decodes: the period clock turns red while transmitting. It is the one thing on the screen that moves, so it is where the eye already is, and “am I on the air” is worth reading from across the room.", + "Auto-call started a call and cut it a second later, then went again. Two faults, both visible in the log: the attempts cap trips WHILE the seventh call is going out — that is what counts it — and it halted the transmitter where it stood, sending half a call; and the station was then picked again four seconds later, because the rest after a series was skipped whenever nothing better was on the air, which for the station everybody is chasing is every period. The cap now lets the over finish before stopping, and a rest is a rest.", + "Chase new now follows a change of hunt (“new” ↔ “new + unconfirmed”). It judges each decode as it arrives and keeps only what was new at that moment, so its list was a set of verdicts reached under the old rule and nothing in it was ever re-judged: switching to “new only” left rows that are merely unconfirmed, and switching the other way could not bring back what had already been refused. The list is emptied on the change and refills from the live feed within a period or two.", + "Halt is now a verdict on the station being auto-called: it is set aside for the session and never called again until auto-call is switched off and on. Halt used to clear the engine’s whole state — rests and rounds along with it — so the station the operator had just stopped was eligible again in the same second and the next period called it straight back. Naming it in the chase list does not override that either.", + "Auto-call can log every decision (Settings → DXHunter, under auto-call): one line per period saying what was on the air, how many stations were callable, why each of the others was refused — worked, nothing needed, mid-QSO, resting, halted, parked, no LoTW — and which ones were ranked. It answers the question the log could not: why nothing was called while the panel showed a screen full of wanted stations. A line every fifteen seconds, so it is off by default.", + "FT decodes: the list empties for a receiver that changes band. What is on 20 m says nothing about 40 m, and half a screen of stations no longer reachable is worse than an empty one — the period headings still march on and the badges still read as verdicts. Per receiver: in a split view the other one has not moved.", + "FT decodes: a pink WL badge marks a station on your watch list, first in the status column. It is your own answer to “is this one worth my time”, and it outranks whatever the log makes of the entity. Matched by the watch list’s own rule — a trailing * is a prefix — so a badge and an alert cannot disagree about the same station.", + "Auto-call: a station CALLING YOU is answered whether or not the log has anything to gain. It was refused when nothing was needed from it — so a QSO would end, two stations would call on the next sequence, and the machine ignored both. Already worked on this band and mode, or stopped by the operator, still means no.", + "Auto-call: a watched callsign now outranks every station that is not on the list, instead of being lifted one rung at a time. A watched DXpedition with nothing left to gain from it sat at the bottom of the ladder and was never reached on a band full of stations that were needed — which is the opposite of what putting it on the list meant. Among watched stations the old order still applies: entity, band, mode, slot.", + "FT decodes: the watch-list badge comes after the LoTW “L”, which is one letter and always in the same place — a badge in front of it shifted the whole status column from row to row.", + "Auto-call stops calling a station that starts working somebody else. Callability was tested when the station was CHOSEN and never again while it was held, so one picked on its CQ that then answered another caller went on being called for the whole seven attempts — two minutes of transmitting at a station in a QSO that cannot hear it. Its final frame to another station does not count: that one is a period from being free, which is the best moment there is to be calling it. Released with no verdict attached, so it is fair game with a full allowance the moment it CQs again.", + "Auto-call now calls only what the decodes list is SHOWING. The filters above it — CQ only, LoTW only, the new-category chips, continents, minimum report, the search box — steer the transmitter as well as the eye: a station filtered off the screen is not called. The panel sends the callsigns it is showing rather than its filter settings, so there is one definition of “shown” and the engine cannot disagree with what is in front of the operator. With the list closed, nothing is filtered.", + "PSK Reporter panel: the suggested call offset now always has an answer when there is data. It looked for a clear run of empty slots and said nothing when it found none — which is exactly the case it was built for: a hundred decodes across a 2800 Hz passband leave no gap at all, and the panel drew the whole histogram with no advice under it. Failing a real gap it now names the quietest slot, ties going to the higher offset, and never above the ceiling.", + "Auto-call refused nearly every station on the air as “already worked”. It was reading the ENTITY’s verdict as the station’s: “worked” there means the COUNTRY is in the log on this band and mode, so on a band where the operator has most of them, everything decoding was turned away — a trace of one evening shows twenty decodes out of twenty-one refused that way, the watched DXpedition among them. The entity’s verdict now decides what is NEEDED, and the callsign’s own slot decides whether calling it would be a duplicate.", + "Auto-call reads a multi-answer line whole. MSHV answers two stations in one transmission and the decoder prints them as one line — “YV5ALI RR73; F4BPO -08” — where the second half is a report to us. Reading only the start of it, the engine saw a station working somebody else and dropped the target at the exact moment the DX was answering. The decodes panel already read every segment; the engine now does too, for “is it calling us”, “can it be called” and “is the QSO finished”.", + "Auto-call leaves the slot after a QSO to your own 73. The DX’s RR73 is decoded while you are between overs, so the decoder is about to send that 73 in the slot just opening — answering somebody else there switched the DX call mid-sequence, and the frame that closes the contact never went out whole. The next station is picked on the following period instead, fifteen seconds later, and it is still there. It used to hand straight on because the slot is the resource; the slot is worth less than the QSO it spoils." + ], + "fr": [ + "Chasse au nouveau : le panneau dit désormais ce que fait le FLUX — actif ou coupé, combien de reports il a pris, sur combien de carrés récepteurs il est abonné. Une liste vide ne disait rien sur le fait qu’il arrive quoi que ce soit, ce qui est la première question quand elle semble morte.", + "Chasse au nouveau : le compteur de l’en-tête comptait ce qui SURVIVAIT aux filtres de catégorie — un panneau contenant douze stations affichait « 0 entendus » filtres éteints. Il affiche maintenant les deux (« 3 sur 12 entendus »), et le pied de panneau prend le rayon dans le flux au lieu d’annoncer ~300 km quel que soit le réglage.", + "Chasse au nouveau : un filtrage qui masque tout n’est plus retenu comme une préférence. Un ensemble enregistré ne contenant aucune des catégories proposées — ce que devient une préférence écrite par une version antérieure après le renommage d’une catégorie — masquait tout le panneau à chaque lancement, sans rien dire.", + "Chasse au nouveau : une sélection de bandes qui lui est propre, sous l’option (160 m à 70 cm). La liste de bandes de la station s’applique toujours en dessous — celle-ci dit ce qu’on veut SURVEILLER ce soir, ce qui est une autre question : une station équipée 2 m et 70 cm qui chasse en HF ne les a plus dans la liste. Une bande décochée est écartée à l’arrivée, elle cesse de prendre une place.", + "L’appel automatique coupait votre propre 73. Au décodage du RRR du DX, le QSO était considéré terminé et la station suivante choisie dans le même instant — or un reply n’est pas mis en file : WSJT-X l’exécute aussitôt, abandonne l’échange et se met à appeler la nouvelle station. Plus rien n’est appelé par-dessus une émission en cours, et le butoir de quatre minutes repart quand la station commence à répondre : un QSO entamé après une longue attente ne peut plus être interrompu en plein échange.", + "Appel automatique : un vrai besoin passe désormais devant un besoin non confirmé de même niveau. En chasse « nouveau + non confirmé », une bande déjà travaillée en attente de QSL est signalée comme besoin — à juste titre — mais ce n’est pas la même prise qu’une bande jamais travaillée, et les deux avaient le même rang : avec une de chaque dans la période, le moteur prenait la plus forte. Un besoin non confirmé descend sous le vrai besoin de son propre niveau, et pas plus bas : une nouvelle bande non confirmée passe toujours devant un vrai nouveau slot.", + "Panneau PSK Reporter : la requête d’historique s’exécute désormais dans LES DEUX portées de flux, et elle est refaite toutes les cinq minutes tant qu’une station est surveillée. Elle n’avait lieu qu’une fois, et seulement sur le flux étroit — une cible choisie peu après l’ouverture du panneau affichait un décodage là où un autre programme, abonné depuis une heure, en montrait quatre. Les stations qui alimentent PSK Reporter envoient par paquets toutes les cinq minutes environ : c’est cette cadence qui est désormais suivie.", + "Panneau PSK Reporter : les textes annonçaient « depuis 5 min » alors que la fenêtre était passée à dix. C’est la fenêtre qui avait changé, pas les phrases.", + "Cluster : « superfox », « super fox », « fox/hound » et « F/H » dans un commentaire de spot sont lus comme du FT8. Ce sont les modes d’émission DXpedition de WSJT-X, pas des modes à part : le commentaire retombait sur le plan de bande et ressortait en DATA — ce qui décidait ensuite du verdict bande+mode, si bien qu’un ZD8 sur 21.071 se lisait comme un nouveau slot DATA au lieu du nouveau FT8 qu’il est.", + "Décodages FT : une colonne récepteur apparaît quand plusieurs décodeurs alimentent une même liste fusionnée. Deux décodeurs sur la même bande envoient les mêmes stations deux fois avec leur propre SNR, et rien sur la ligne ne disait laquelle était laquelle.", + "L’appel automatique répondait avec quatre à six secondes de retard, si bien que le décodeur commençait son appel bien après le début du créneau. Une période était jugée un créneau entier plus quatre secondes après L’HORODATAGE DU DÉCODAGE — c’est-à-dire le début de ce créneau, treize secondes avant que ses décodages existent. Elle est désormais jugée 0,8 s après l’ARRIVÉE du dernier décodage, et le balayeur tourne quatre fois par seconde au lieu d’une fois toutes les deux : la réponse part dans la seconde où les décodages sont tombés, ce qu’exige la frontière du créneau suivant.", + "Décodages FT : l’horloge de période passe au rouge pendant l’émission. C’est le seul élément qui bouge à l’écran, donc là où l’œil se trouve déjà, et « suis-je en émission » mérite d’être lisible de l’autre bout de la pièce.", + "L’appel automatique lançait un appel puis le coupait une seconde plus tard, avant de repartir. Deux fautes, toutes deux lisibles dans le journal : le plafond d’appels tombe PENDANT l’émission du septième appel — c’est elle qui le compte — et il coupait la porteuse sur place, envoyant un demi-appel ; puis la station était reprise quatre secondes après, parce que le repos suivant une série était ignoré dès que rien de mieux n’était sur l’air, ce qui, pour la station que tout le monde chasse, est le cas à chaque période. Le plafond laisse désormais l’émission en cours se terminer, et un repos est un repos.", + "La chasse au nouveau suit désormais un changement de chasse (« nouveau » ↔ « nouveau + non confirmé »). Elle juge chaque décodage à son arrivée et ne garde que ce qui était nouveau à cet instant : sa liste était donc un ensemble de verdicts rendus sous l’ancienne règle, que rien ne rejugeait — passer en « nouveau seulement » y laissait des lignes simplement non confirmées, et l’inverse ne pouvait pas ramener ce qui avait déjà été refusé. La liste est vidée au changement et se remplit à nouveau depuis le flux en une période ou deux.", + "Le Stop devient un verdict sur la station appelée automatiquement : elle est mise de côté pour la session et n’est plus appelée jusqu’à ce que l’appel automatique soit éteint puis rallumé. Le Stop remettait tout le moteur à zéro — repos et séries compris — si bien que la station qu’on venait d’arrêter redevenait éligible dans la seconde et que la période suivante la rappelait. La nommer dans la liste de chasse ne passe pas outre non plus.", + "L’appel automatique peut journaliser chaque décision (Réglages → DXHunter, sous l’appel automatique) : une ligne par période disant ce qu’il y avait sur l’air, combien de stations étaient appelables, pourquoi chacune des autres a été écartée — travaillée, rien à y gagner, en QSO, au repos, arrêtée par l’opérateur, épuisée, sans LoTW — et lesquelles ont été classées. Elle répond à la question à laquelle le journal ne pouvait pas répondre : pourquoi rien n’a été appelé alors que le panneau était plein de stations voulues. Une ligne toutes les quinze secondes : désactivé par défaut.", + "Décodages FT : la liste se vide pour un récepteur qui change de bande. Ce qu’il y a sur 20 m ne dit rien du 40 m, et un demi-écran de stations devenues inatteignables est pire qu’un écran vide — les en-têtes de période continuent de défiler et les pastilles se lisent toujours comme des verdicts. Par récepteur : en vue scindée, l’autre n’a pas bougé.", + "Décodages FT : une pastille rose WL signale une station de votre liste de surveillance, en tête de la colonne Statut. C’est votre propre réponse à « celle-là vaut-elle mon temps », et elle passe devant ce que le log pense de l’entité. Reconnue par la règle de la liste elle-même — un * final est un préfixe — pour qu’une pastille et une alerte ne puissent pas se contredire.", + "Appel automatique : une station qui VOUS appelle est prise, que le log ait quelque chose à y gagner ou non. Elle était écartée quand il n’y avait rien à en tirer : un QSO se terminait, deux stations appelaient à la séquence suivante, et la machine les ignorait toutes les deux. Déjà travaillée sur cette bande et ce mode, ou arrêtée par l’opérateur, veut toujours dire non.", + "Appel automatique : un indicatif surveillé passe désormais devant toute station absente de la liste, au lieu d’être remonté d’un cran à la fois. Une DXpédition surveillée dont on n’avait plus besoin restait au bas de l’échelle et n’était jamais atteinte sur une bande pleine de stations utiles — l’inverse de ce que veut dire l’avoir mise sur la liste. Entre stations surveillées, l’ordre d’avant s’applique toujours : entité, bande, mode, slot.", + "Décodages FT : la pastille de liste de surveillance passe après le « L » de LoTW, qui tient en une lettre et reste toujours au même endroit — une pastille devant lui décalait toute la colonne Statut d’une ligne à l’autre.", + "L’appel automatique cesse d’appeler une station qui se met à travailler quelqu’un d’autre. Le fait d’être appelable était vérifié au moment de la CHOISIR et jamais ensuite pendant qu’on la tenait : une station prise sur son CQ qui répondait à un autre appelant continuait d’être appelée pendant les sept essais — deux minutes d’émission vers une station en QSO qui ne peut pas l’entendre. Sa trame finale vers un autre ne compte pas : celle-là est à une période d’être libre, c’est le meilleur moment pour l’appeler. Relâchée sans verdict, elle redevient appelable avec un quota complet dès qu’elle repasse CQ.", + "L’appel automatique n’appelle plus que ce que la liste des décodages AFFICHE. Les filtres au-dessus d’elle — CQ seuls, LoTW, les pastilles de catégorie, continents, report minimal, la recherche — pilotent l’émetteur autant que l’œil : une station filtrée hors écran n’est pas appelée. Le panneau envoie les indicatifs qu’il montre et non ses réglages de filtre, si bien qu’il n’existe qu’une définition de « affiché » et que le moteur ne peut pas contredire ce que l’opérateur a sous les yeux. La liste fermée, rien n’est filtré.", + "Panneau PSK Reporter : le créneau d’appel suggéré donne désormais toujours une réponse quand il y a des données. Il cherchait une suite de créneaux vides et se taisait quand il n’en trouvait pas — c’est-à-dire précisément dans le cas pour lequel il existe : cent décodages sur un passe-bande de 2800 Hz ne laissent aucun trou, et le panneau dessinait tout l’histogramme sans un mot de conseil dessous. À défaut de vrai trou, il nomme le créneau le moins encombré, les égalités allant vers le haut, et jamais au-dessus du plafond.", + "L’appel automatique écartait presque toutes les stations sur l’air comme « déjà travaillées ». Il lisait le verdict de l’ENTITÉ comme celui de la station : « travaillé » y signifie que le PAYS est au log sur cette bande et ce mode, donc sur une bande où l’opérateur les a presque tous, tout ce qui décodait était refusé — une trace d’une soirée montre vingt décodages sur vingt et un écartés ainsi, la DXpédition surveillée parmi eux. Le verdict de l’entité décide désormais de ce qui est NÉCESSAIRE, et le créneau propre à l’indicatif décide si l’appeler ferait un doublon.", + "L’appel automatique lit une trame multi-réponses en entier. MSHV répond à deux stations dans une même émission et le décodeur l’imprime sur une seule ligne — « YV5ALI RR73; F4BPO -08 » — dont la seconde moitié est un report pour nous. En n’en lisant que le début, le moteur voyait une station occupée avec quelqu’un d’autre et lâchait la cible au moment précis où le DX répondait. Le panneau des décodages lisait déjà chaque segment ; le moteur le fait désormais aussi, pour « est-ce qu’il nous appelle », « peut-on l’appeler » et « le QSO est-il fini ».", + "L’appel automatique laisse à votre 73 le créneau qui suit un QSO. Le RR73 du DX est décodé pendant que vous êtes entre deux émissions : le décodeur s’apprête à envoyer ce 73 dans le créneau qui s’ouvre, et y répondre à quelqu’un d’autre changeait l’indicatif en pleine séquence — la trame qui clôt le contact ne partait jamais entière. La station suivante est désormais choisie à la période d’après, quinze secondes plus tard, et elle est toujours là. Il enchaînait aussitôt parce que le créneau est la ressource ; le créneau vaut moins que le QSO qu’il gâche." + ] + }, { "version": "0.27.12", "date": "", diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 0332dcb..3bf80f9 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -54,7 +54,7 @@ import { GetFlexState, FlexAmpOperate, GetPSKReporterStatus, GetLiveOpenings, GetChaseNew, QSLViaRepairStatus, RepairQSLVia, DismissQSLViaRepair, - GetAutoCallStatus, SetAutoCall, SetAutoCallOnly, TakeAutoCallTarget, ResetAutoCall, + GetAutoCallStatus, SetAutoCall, SetAutoCallOnly, TakeAutoCallTarget, HaltAutoCall, WatchlistEntries, } from '../wailsjs/go/main/App'; import { Combobox } from '@/components/ui/combobox'; import { applyAwardRefs, parseAwardRefs as parseManualRefs, spotRefList , withIOTARef, withRDARef } from '@/lib/awardRefs'; @@ -123,6 +123,7 @@ import { GridSquareMap } from '@/components/GridSquareMap'; import { loadClusterMacros, visibleClusterMacros } from '@/lib/clusterMacros'; import { DecodesPanel, type Decode as DecodeRow, type TxMsg as TxMsgRow } from '@/components/DecodesPanel'; import { PSKReporterPanel } from '@/components/PSKReporterPanel'; +import { decoderName } from '@/lib/decoderName'; import { subscribeRotorHeading, pokeRotorHeading } from '@/lib/rotorHeading'; import { writeUiPref } from '@/lib/uiPref'; import { formatDateTimeUTC } from '@/lib/dateFormat'; @@ -2406,6 +2407,17 @@ export default function App() { // the watchlist panel: a call can be added from the cluster or the // DXpeditions tab, and the old inline message lived on a page nobody was // looking at. + // The watch list as patterns, for the decodes badge. Loaded once and kept in + // step with the same event the notice below listens to — a call added from + // the cluster has to light up in the decodes list too. + const [watchPatterns, setWatchPatterns] = useState([]); + const loadWatchPatterns = useCallback(() => { + WatchlistEntries() + .then((es: any[]) => setWatchPatterns((es ?? []).map((e: any) => String(e?.callsign ?? '')).filter(Boolean))) + .catch(() => {}); + }, []); + useEffect(() => { loadWatchPatterns(); }, [loadWatchPatterns]); + useEffect(() => EventsOn('watchlist:changed', () => loadWatchPatterns()), [loadWatchPatterns]); const [wlNotice, setWlNotice] = useState<{ call: string; added: boolean } | null>(null); const wlNoticeTimer = useRef(undefined); useEffect(() => EventsOn('watchlist:changed', (e: any) => { @@ -2597,6 +2609,9 @@ export default function App() { // Staged like the cluster's, so a period arriving as one burst of fifty // packets costs one status lookup and one render, not fifty of each. const pendingDecodesRef = useRef([]); + // The band each receiver was last decoding on. A band change empties that + // receiver's list — see the flush below. + const decoderBandRef = useRef>(new Map()); const pendingDecodeTimer = useRef(undefined); // Rotor quick-turn buttons (Settings → Rotator). Same reload trigger as the @@ -3718,9 +3733,42 @@ export default function App() { }); } } catch { /* status unresolved — the decode still shows, just unflagged */ } + // A BAND CHANGE empties that receiver's list. + // + // What is on 20 m says nothing about 40 m, and half a screen of stations + // that are no longer reachable is worse than an empty one: the period + // headings still march on, the rows still carry status badges, and the + // operator reads a band they have left. Per receiver — in a split view + // the other one has not moved — and only when the new band is known. + const moved = new Map(); + for (const d of batch) { + const inst = d.instance ?? ''; + const band = (d.band ?? '').toLowerCase(); + if (!band) continue; + const was = decoderBandRef.current.get(inst); + decoderBandRef.current.set(inst, band); + if (was && was !== band) moved.set(inst, band); + } + if (moved.size > 0) { + for (const [inst, band] of moved) { + LogUIError('decodes', `${decoderName(inst) || 'decoder'} moved to ${band.toUpperCase()} — its earlier decodes cleared`, ''); + } + setTxMsgs((arr) => arr.filter((m) => !moved.has(m.instance ?? ''))); + } setDecodes((arr) => { const cutoff = Date.now() - DECODE_KEEP_MS; - const next = [...arr, ...batch].filter((d) => Date.parse(d.at) >= cutoff); + // Rows from a receiver that has just changed band go with it — the + // batch itself is already on the new band. + const kept = moved.size === 0 ? arr : arr.filter((d) => !moved.has(d.instance ?? '')); + // The batch can straddle the change — a 300 ms flush can hold the last + // decodes of the old band and the first of the new. Only the new band + // survives for a receiver that moved. + const fresh = moved.size === 0 ? batch + : batch.filter((d) => { + const b2 = moved.get(d.instance ?? ''); + return !b2 || (d.band ?? '').toLowerCase() === b2; + }); + const next = [...kept, ...fresh].filter((d) => Date.parse(d.at) >= cutoff); return next; }); }; @@ -6385,15 +6433,16 @@ export default function App() { // An empty instance lets the backend fall back to whichever application // last reported its status — the normal single-receiver case. onHalt={(instance) => { - // Halt means stop, including whatever auto-call had started — and it - // clears the engine's state, so a target it had given up on is not - // still sitting there when the operator switches it back on. - ResetAutoCall().catch(() => {}); + // Halt is a verdict on the station being called: it is set aside for + // the session rather than released, or the next period would call it + // straight back — which is exactly what the operator just stopped. + HaltAutoCall().catch(() => {}); HaltDecodeTx(instance, false).catch((e: any) => setError(String(e?.message ?? e))); }} autoCallOn={!!autoCallStatus?.enabled} onToggleAutoCall={toggleAutoCall} autoCall={autoCallStatus} + watchlist={watchPatterns} autoCallOnly={autoCallStatus?.only ?? ''} onSetAutoCallOnly={(list) => { setAutoCallStatus((st: any) => ({ ...st, only: list.toUpperCase() })); diff --git a/frontend/src/components/ChaseNewPanel.tsx b/frontend/src/components/ChaseNewPanel.tsx index 9a68754..d27260c 100644 --- a/frontend/src/components/ChaseNewPanel.tsx +++ b/frontend/src/components/ChaseNewPanel.tsx @@ -15,7 +15,7 @@ import { useI18n } from '@/lib/i18n'; import { chaseAllows } from '@/lib/spotDisplay'; import { markerColour } from '@/lib/spotMarkers'; import { cn } from '@/lib/utils'; -import { GetChaseNewSpots } from '../../wailsjs/go/main/App'; +import { GetChaseNewSpots, GetPSKReporterStatus } from '../../wailsjs/go/main/App'; export interface ChaseNewSpot { call: string; @@ -80,15 +80,25 @@ function categoryOf(s: ChaseNewSpot): Category | null { const FILTER_KEY = 'opslog.chaseNewFilters'; +function allowedCategories(): Category[] { + return CATEGORIES.filter((c) => chaseAllows(c.key)).map((c) => c.key); +} + function loadFilters(): Set { + const allowed = allowedCategories(); try { const raw = localStorage.getItem(FILTER_KEY); if (raw) { const list = JSON.parse(raw) as Category[]; - if (Array.isArray(list)) return new Set(list); + // A stored set holding NONE of the categories on offer hides the whole + // panel, for ever, with nothing to say why — and that is exactly what a + // preference written by an older build does once a category is renamed. + // Treated as "no preference": a panel that shows nothing at every launch + // is never what was meant, and the chips are one click away. + if (Array.isArray(list) && list.some((k) => allowed.includes(k))) return new Set(list); } } catch { /* a corrupt preference is not worth a broken panel */ } - return new Set(CATEGORIES.filter((c) => chaseAllows(c.key)).map((c) => c.key)); + return new Set(allowed); } export function ChaseNewPanel({ onPick, onClose }: Props) { @@ -96,6 +106,10 @@ export function ChaseNewPanel({ onPick, onClose }: Props) { const [spots, setSpots] = useState([]); const [loaded, setLoaded] = useState(false); const [on, setOn] = useState>(loadFilters); + // The FEED, not the list: connected or not, how many reports it has taken, + // and what it is filtered on. Without it an empty panel says nothing about + // whether anything is arriving at all — which is the first question. + const [feed, setFeed] = useState(null); // Polled rather than pushed: the feed can deliver several a second under an // opening, and an event per row would be a redraw per row for a list nobody @@ -106,6 +120,8 @@ export function ChaseNewPanel({ onPick, onClose }: Props) { try { const r = ((await GetChaseNewSpots()) ?? []) as ChaseNewSpot[]; if (alive) { setSpots(r); setLoaded(true); } + const st = await GetPSKReporterStatus(); + if (alive) setFeed(st); } catch { /* the feed may not be up yet */ } }; tick(); @@ -154,8 +170,13 @@ export function ChaseNewPanel({ onPick, onClose }: Props) { ))} + {/* Both numbers: what is on screen, and what was heard. They differ + exactly when a category is switched off, which is the one case an + operator reads this panel as broken. */} - {loaded ? t('chn.count', { n: shown.length }) : ''} + {loaded ? (shown.length === spots.length + ? t('chn.count', { n: spots.length }) + : t('chn.countOf', { n: shown.length, total: spots.length })) : ''} {onClose && ( + ); + })} + + +

{t('chn.bandsHint')}

+ + )} {/* PSK Reporter analysis of the station being called. Same service as @@ -5505,6 +5551,20 @@ function SettingsModalImpl({ onClose, onSaved, initialSection, onMainPaneChanged ))} + {/* A rule about what the transmitter may call, stated once. The + decodes panel has a LoTW chip too, but that one is a way of + READING the band — it is flicked on and off while looking + around, and the panel can be closed. */} + + )} diff --git a/frontend/src/lib/i18n.tsx b/frontend/src/lib/i18n.tsx index eb3be68..a5d8abc 100644 --- a/frontend/src/lib/i18n.tsx +++ b/frontend/src/lib/i18n.tsx @@ -164,12 +164,12 @@ const en: Dict = { // PSK Reporter panel beside the FT decodes — "can he hear me?" 'psk.title': 'PSK Reporter', 'psk.show': 'Show the PSK Reporter analysis', 'psk.hide': 'Hide the PSK Reporter analysis', 'psk.online': 'online', 'psk.offline': 'offline', 'psk.off': 'disabled', - 'psk.spots': '{n} reports', 'psk.spotsTip': 'Reports received about this station in the last 5 minutes — the feed is alive even when the counters below are zero.', + 'psk.spots': '{n} reports', 'psk.spotsTip': 'Reports received about this station in the last 10 minutes — the feed is alive even when the counters below are zero.', 'psk.enableHint': 'Switch on PSK Reporter analysis in Settings → FTx decodes to see whether the station you are calling can hear you.', 'psk.pickHint': 'Click a decode to analyse the path to that station.', 'psk.heardYou': 'He decoded you {s}s ago', 'psk.pathOpen': 'Path open from your area', 'psk.pathOpenSub': 'he decoded {n} station(s) near you', 'psk.notYet': 'Not yet decoded', - 'psk.notYetSub': 'No path confirmation in the last 5 min', + 'psk.notYetSub': 'No path confirmation in the last 10 min', 'psk.nearHim': 'Your signal reported near him ({g})', 'psk.nRx': '{n} receiver(s)', 'psk.tFromArea': 'From your area', 'psk.tFromAreaFoot': 'decoded by him', 'psk.tPileup': 'Pileup', 'psk.tPileupFoot': 'active (2 min)', @@ -179,7 +179,7 @@ const en: Dict = { 'psk.tCallers': 'Callers you hear', 'psk.tCallersFoot': 'your competition', 'psk.tCallersFootConf': 'of which he decoded', 'psk.tCallersTip': 'Stations in your own decodes calling him. The number in brackets is how many of them he has decoded — those are the ones actually reaching him.', 'psk.uploads': 'He uploads to PSK Reporter', 'psk.noUploads': '{c} does not upload to PSK Reporter', - 'psk.fromAreaList': 'From your area — his recent reports', 'psk.fromAreaEmpty': 'He has decoded nobody near you in the last 5 min.', + 'psk.fromAreaList': 'From your area — his recent reports', 'psk.fromAreaEmpty': 'He has decoded nobody near you in the last 10 min.', 'psk.rowTip': '{c} ({g}) — decoded by him {s}s ago at {d} dB. The offset is where the signal landed in HIS passband, not necessarily where they were calling.', 'psk.passband': 'Passband (audio Hz)', 'psk.ceiling': 'ceiling ≈ {hz} Hz · {n} decodes / 10 min', 'psk.noDial': 'no offsets (dial unknown)', 'psk.noDecodes': 'no decodes yet', 'psk.tryOffset': 'Try +{hz} Hz', @@ -190,10 +190,10 @@ const en: Dict = { // Auto-call — the engine that answers a decode on its own (internal/autocall) 'ac.enable': 'Auto-call', 'ac.enableHint': 'answer the best station on the air without clicking it', 'ac.warn': 'This keys your transmitter without asking. It answers one station at a time, never over a QSO in progress, and gives up on its own — 7 calls, or 15 for a watched callsign, and 3 of the station\'s own periods with no decode of it. Every decision is written to the log file with its reason, and Halt stops it.', - 'ac.ladder': 'Order of priority: watched new entity, new entity, watched new band, new band, watched new mode, new mode, watched new slot, new slot, then a watched callsign with nothing needed. A station in the middle of a QSO with somebody else is never called — it cannot answer.', + 'ac.ladder': 'Order of priority: every callsign on the watch list first (among themselves: new entity, band, mode, slot), then new entity, new band, new mode, new slot. A real need comes before one that exists only because a QSL never came. A station calling YOU is answered whatever the log makes of it. A station in the middle of a QSO with somebody else is never called — it cannot answer.', 'ac.only': 'Chase only', 'ac.onlyPh': 'VP6D 3Y0J (space or comma)', 'ac.onlyHint': 'one or more callsigns — nothing else is called; empty means call whatever the log needs', 'ac.attempts': 'Calls before giving up', 'ac.watchedAttempts': 'if watched', 'ac.misses': 'Missed periods', - 'ac.rounds': 'Series per station', 'ac.rest': 'Rest (min)', + 'ac.rounds': 'Series per station', 'ac.rest': 'Rest (min)', 'ac.trace': 'Log every decision', 'ac.traceHint': '— one line per period in the diagnostic log: what was on the air, why each station was refused, and what was decided. For working out why nothing is being called. A line every fifteen seconds, so leave it off otherwise.', 'ac.onScreen': 'Call only what the decodes list is showing', 'ac.onScreenHint': '— the filters above the list (CQ only, LoTW only, the new-category chips, continents, report, search) steer the transmitter too: a station filtered off the screen is not called. With the list closed nothing is filtered.', 'dec.chasePh': 'chase…', 'dec.chaseTip': 'Call ONLY these stations — one or more callsigns, spaces or commas. Empty means call whatever the log needs. Same field as Preferences → DXHunter.', 'dec.autoStoppedTip': 'Auto-call has given up on a station from the chase list — click to switch it off, or Halt to clear it.', // Contest watchlist (Settings → DXHunter): how a special-event fleet joins the list on its own 'wlc.title': 'Contest', 'wlc.pattern': 'Auto-add on', 'wlc.patternPh': 'WWA', 'wlc.patternHint': 'any spotted callsign CONTAINING this joins the watchlist as a contest entry (TM29WWA, HB9WWA, F4WWA/P)', @@ -213,7 +213,7 @@ const en: Dict = { 'dec.colFreq': 'Freq', 'dec.colFreqTitle': 'Audio offset inside the passband (Hz)', 'dec.txNow': 'Transmitting', 'dec.txIdle': 'Transmit', 'dec.working': 'calling', 'dec.toYou': 'to you', 'dec.txUnknown': 'transmitting — text not reported', 'dec.txNothing': 'nothing being sent', - 'dec.colTime': 'Time', 'dec.colSnr': 'SNR', 'dec.colMsg': 'Message', 'dec.bgGridUnconf': 'GRID?', 'dec.bgGridUnconfTip': 'This square is worked but not yet confirmed — a QSL to chase, not a QSO to make.', 'dec.colGrid': 'Grid', 'dec.colState': 'State', 'dec.colCountry': 'Country', 'dec.colBand': 'Band', 'dec.colMode': 'Mode', 'dec.colStatus': 'Status', 'dec.stateTip': 'US state', 'dec.wkd': 'Wkd', + 'dec.colTime': 'Time', 'dec.colRx': 'RX', 'dec.colSnr': 'SNR', 'dec.colMsg': 'Message', 'dec.bgGridUnconf': 'GRID?', 'dec.bgGridUnconfTip': 'This square is worked but not yet confirmed — a QSL to chase, not a QSO to make.', 'dec.colGrid': 'Grid', 'dec.colState': 'State', 'dec.colCountry': 'Country', 'dec.colBand': 'Band', 'dec.colMode': 'Mode', 'dec.colStatus': 'Status', 'dec.stateTip': 'US state', 'dec.wl': 'WL', 'dec.wlTip': 'On your watch list', 'dec.wkd': 'Wkd', 'dec.bgPota': 'POTA', 'dec.bgGrid': 'GRID', 'dec.bgPfx': 'PFX', 'dec.bgState': 'New State', 'dec.bgCounty': 'CTY', 'dec.stNew': 'NEW', 'dec.stBand': 'BAND', 'dec.stMode': 'MODE', 'dec.stSlot': 'SLOT', 'dec.stCall': 'CALL', 'dec.empty': 'Nothing decoded yet. Decodes arrive from WSJT-X, JTDX or MSHV over the inbound UDP link (Settings -> UDP).', @@ -556,7 +556,7 @@ const en: Dict = { // Grids column headers (recent / worked-before / cluster) 'rqg.c.number': 'QSO number', 'rqg.h.number': 'N°', 'rqg.c.qso_date': 'Date UTC', 'rqg.c.qso_date_off': 'Date off', 'rqg.c.callsign': 'Callsign', 'rqg.c.band': 'Band', 'rqg.c.band_rx': 'Band RX', 'rqg.c.mode': 'Mode', 'rqg.c.submode': 'Submode', 'rqg.c.freq_hz': 'Freq (TX)', 'rqg.h.freq_hz': 'Freq', 'rqg.c.freq_rx_hz': 'Freq (RX)', 'rqg.h.freq_rx_hz': 'Freq RX', 'rqg.c.rst_sent': 'RST sent', 'rqg.c.rst_rcvd': 'RST rcvd', 'rqg.c.tx_pwr': 'TX Power', 'rqg.c.name': 'Name', 'rqg.c.qth': 'QTH', 'rqg.c.address': 'Address', 'rqg.c.country': 'Country', 'rqg.c.state': 'State', 'rqg.c.cnty': 'County', 'rqg.c.cont': 'Continent', 'rqg.h.cont': 'Cont', 'rqg.c.grid': 'Grid', 'rqg.c.gridsquare_ext': 'Grid Ext', 'rqg.h.gridsquare_ext': 'GridExt', 'rqg.c.vucc_grids': 'VUCC grids', 'rqg.h.vucc_grids': 'VUCC', 'rqg.c.dxcc': 'DXCC #', 'rqg.c.cqz': 'CQZ', 'rqg.c.ituz': 'ITU', 'rqg.c.iota': 'IOTA', 'rqg.c.sota_ref': 'SOTA ref', 'rqg.h.sota_ref': 'SOTA', 'rqg.c.pota_ref': 'POTA ref', 'rqg.h.pota_ref': 'POTA', 'rqg.c.age': 'Age', 'rqg.c.lat': 'Lat', 'rqg.c.lon': 'Lon', 'rqg.c.distance_km': 'Distance (km)', 'rqg.h.distance_km': 'Dist km', 'rqg.c.email': 'Email', 'rqg.c.web': 'Web', 'rqg.c.qsl_sent': 'QSL sent', 'rqg.c.qsl_rcvd': 'QSL rcvd', 'rqg.c.qsl_sent_date': 'QSL sent date', 'rqg.h.qsl_sent_date': 'QSL S date', 'rqg.c.qsl_rcvd_date': 'QSL rcvd date', 'rqg.h.qsl_rcvd_date': 'QSL R date', 'rqg.c.qsl_via': 'QSL via (manager)', 'rqg.c.qsl_sent_via': 'QSL sent via', 'rqg.h.qsl_sent_via': 'Sent via', 'rqg.c.qsl_rcvd_via': 'QSL rcvd via', 'rqg.h.qsl_rcvd_via': 'Rcvd via', 'rqg.c.qsl_msg': 'QSL msg', 'rqg.c.qslmsg_rcvd': 'QSL msg rcvd', 'rqg.c.lotw_sent': 'LoTW sent', 'rqg.c.lotw_rcvd': 'LoTW rcvd', 'rqg.c.lotw_sent_date': 'LoTW sent date', 'rqg.h.lotw_sent_date': 'LoTW S date', 'rqg.c.lotw_rcvd_date': 'LoTW rcvd date', 'rqg.h.lotw_rcvd_date': 'LoTW R date', 'rqg.c.eqsl_sent': 'eQSL sent', 'rqg.c.eqsl_rcvd': 'eQSL rcvd', 'rqg.c.eqsl_sent_date': 'eQSL sent date', 'rqg.h.eqsl_sent_date': 'eQSL S date', 'rqg.c.eqsl_rcvd_date': 'eQSL rcvd date', 'rqg.h.eqsl_rcvd_date': 'eQSL R date', 'rqg.c.opslog_qsl_card_sent': 'OpsLog QSL', 'rqg.c.hamlog_sent': 'HAMLOG.online sent', 'rqg.h.hamlog_sent': 'HAMLOG sent', 'rqg.c.hamlog_sent_date': 'HAMLOG.online sent date', 'rqg.h.hamlog_sent_date': 'HAMLOG sent date', 'rqg.c.hamlog_rcvd': 'HAMLOG.online received', 'rqg.h.hamlog_rcvd': 'HAMLOG rcvd', 'rqg.c.hamlog_rcvd_date': 'HAMLOG.online received date', 'rqg.h.hamlog_rcvd_date': 'HAMLOG rcvd date', 'rqg.c.opslog_qsl_card_rcvd': 'OpsLog QSL rcvd', 'rqg.c.opslog_recording_sent': 'Recording sent', 'rqg.h.opslog_recording_sent': 'Rec sent', 'rqg.c.clublog_sent': 'ClubLog sent', 'rqg.c.clublog_sent_date': 'ClubLog sent date', 'rqg.h.clublog_sent_date': 'ClubLog S date', 'rqg.c.clublog_rcvd': 'ClubLog match', 'rqg.c.clublog_rcvd_date': 'ClubLog match date', 'rqg.h.clublog_rcvd_date': 'ClubLog M date', 'rqg.c.hrdlog_sent': 'HRDLog sent', 'rqg.c.hrdlog_sent_date': 'HRDLog sent date', 'rqg.h.hrdlog_sent_date': 'HRDLog S date', 'rqg.c.qrz_sent': 'QRZ.com sent', 'rqg.c.qrz_rcvd': 'QRZ.com rcvd', 'rqg.c.qrz_sent_date': 'QRZ.com sent date', 'rqg.h.qrz_sent_date': 'QRZ.com S date', 'rqg.c.qrz_rcvd_date': 'QRZ.com rcvd date', 'rqg.h.qrz_rcvd_date': 'QRZ.com R date', 'rqg.c.contest_id': 'Contest ID', 'rqg.h.contest_id': 'Contest', 'rqg.c.srx': 'SRX', 'rqg.c.stx': 'STX', 'rqg.c.srx_string': 'SRX string', 'rqg.h.srx_string': 'SRX str', 'rqg.c.stx_string': 'STX string', 'rqg.h.stx_string': 'STX str', 'rqg.c.check': 'Check', 'rqg.c.precedence': 'Precedence', 'rqg.c.arrl_sect': 'ARRL section', 'rqg.h.arrl_sect': 'ARRL sect', 'rqg.c.prop_mode': 'Prop mode', 'rqg.h.prop_mode': 'Prop', 'rqg.c.sat_name': 'Sat name', 'rqg.h.sat_name': 'Sat', 'rqg.c.sat_mode': 'Sat mode', 'rqg.c.ant_az': 'Ant az', 'rqg.h.ant_az': 'Az', 'rqg.c.ant_el': 'Ant el', 'rqg.h.ant_el': 'El', 'rqg.c.ant_path': 'Ant path', 'rqg.h.ant_path': 'Path', 'rqg.c.station_callsign': 'Station call', 'rqg.h.station_callsign': 'Station', 'rqg.c.operator': 'Operator', 'rqg.c.my_grid': 'My grid', 'rqg.c.my_country': 'My country', 'rqg.h.my_country': 'My ctry', 'rqg.c.my_state': 'My state', 'rqg.c.my_cnty': 'My county', 'rqg.h.my_cnty': 'My cnty', 'rqg.c.my_iota': 'My IOTA', 'rqg.c.my_sota': 'My SOTA', 'rqg.c.my_pota': 'My POTA', 'rqg.c.my_dxcc': 'My DXCC', 'rqg.h.my_dxcc': 'My DXCC#', 'rqg.c.my_cq_zone': 'My CQ zone', 'rqg.h.my_cq_zone': 'My CQZ', 'rqg.c.my_itu_zone': 'My ITU zone', 'rqg.h.my_itu_zone': 'My ITU', 'rqg.c.my_lat': 'My lat', 'rqg.c.my_lon': 'My lon', 'rqg.c.my_street': 'My street', 'rqg.h.my_street': 'Street', 'rqg.c.my_city': 'My city', 'rqg.h.my_city': 'City', 'rqg.c.my_zip': 'My ZIP', 'rqg.h.my_zip': 'ZIP', 'rqg.c.my_rig': 'My rig', 'rqg.c.my_antenna': 'My antenna', 'rqg.c.my_name': 'My name', 'rqg.c.my_wwff': 'My WWFF', 'rqg.c.my_sig': 'My SIG', 'rqg.c.my_sig_info': 'My SIG info', 'rqg.c.my_arrl_sect': 'My ARRL sect', 'rqg.c.my_darc_dok': 'My DARC DOK', 'rqg.c.my_vucc_grids': 'My VUCC grids', 'rqg.h.my_antenna': 'My ant', 'rqg.c.comment': 'Comment', 'rqg.c.notes': 'Notes', 'rqg.c.created': 'Created', 'rqg.h.created': 'Created at', 'rqg.c.updated': 'Updated', 'rqg.h.updated': 'Updated at', 'rqg.grpQso': 'QSO', 'rqg.grpContacted': 'Contacted', 'rqg.grpQsl': 'QSL', 'rqg.grpLotw': 'LoTW', 'rqg.grpEqsl': 'eQSL', 'rqg.grpUploads': 'Uploads', 'rqg.grpContest': 'Contest', 'rqg.grpProp': 'Propagation', 'rqg.grpMyStation': 'My station', 'rqg.grpMisc': 'Misc', 'rqg.grpAwards': 'Awards', 'rqg.awardTip': '{name} — reference this QSO counts for', 'rqg.clearFiltersTitle': 'Clear all column filters', 'rqg.clearFilters': 'Clear filters', 'rqg.selectedCount': '{n} selected', 'rqg.selectAll': 'Select all', 'rqg.selectAllTitle': 'Select every displayed row (respects active column filters)', 'rqg.unselectAll': 'Unselect all', 'rqg.unselectAllTitle': 'Clear the selection', 'rqg.columns': 'Columns', 'rqg.pickerDesc': 'Pick the columns you want visible in the Recent QSOs table. Your selection is saved.', 'rqg.pickerAllGroups': 'All groups:', 'rqg.all': 'all', 'rqg.none': 'none', 'rqg.resetDefaults': 'Reset to defaults', 'rqg.done': 'Done', 'wbg.awardTip': '{name} — reference this QSO counts for', 'wbg.typeCall': 'Type a callsign in the entry strip to see prior contacts.', 'wbg.checking': 'checking…', 'wbg.new': 'NEW', 'wbg.noPriorPre': 'No prior QSO with ', 'wbg.noPriorPost': '.', 'wbg.workedBefore': 'Worked before', 'wbg.first': 'First:', 'wbg.last': 'Last:', 'wbg.dxcc': 'DXCC:', 'wbg.entityQsos': '{n} entity QSOs', 'wbg.clearFiltersTitle': 'Clear all column filters', 'wbg.clearFilters': 'Clear filters', 'wbg.columns': 'Columns', 'wbg.olderQsos': '+ {n} older QSOs (not shown — capped for performance)', 'wbg.pickerTitle': 'Worked-before columns', 'wbg.pickerDesc': 'Pick the columns you want visible in the Worked-before table.', 'wbg.allGroups': 'All groups:', 'wbg.all': 'all', 'wbg.none': 'none', 'wbg.grpAwards': 'Awards', 'wbg.resetDefaults': 'Reset to defaults', 'wbg.done': 'Done', - 'chn.title': 'Chase new', 'chn.close': 'Hide the panel', 'chn.filterHint': 'Show or hide this kind', 'chn.allFiltered': 'Everything heard is filtered out — turn a category back on above.', 'chn.toggle': 'Chase new', 'chn.count': '{n} heard', 'chn.loading': 'Waiting for the feed…', 'chn.empty': 'Nothing new being decoded near you right now.', 'chn.digitalOnly': 'PSK Reporter — digital modes only, heard within ~300 km of you.', 'chn.nearKmHint': "— how far a receiver may be and still count as near you. Shared with the band-opening watch.", 'chn.option': 'Chase new (PSK Reporter)', 'chn.optionHelp': 'Lists stations being decoded near you that are new against your log — new entity, band, mode, slot, prefix or square. Digital modes only.', 'chn.show': 'Chase new panel', + 'chn.title': 'Chase new', 'chn.close': 'Hide the panel', 'chn.filterHint': 'Show or hide this kind', 'chn.allFiltered': 'Everything heard is filtered out — turn a category back on above.', 'chn.toggle': 'Chase new', 'chn.loading': 'Waiting for the feed…', 'chn.empty': 'Nothing new being decoded near you right now.', 'chn.count': '{n} heard', 'chn.countOf': '{n} of {total} heard', 'chn.feedOn': 'feed up · {n} reports · {sq} squares', 'chn.feedOff': 'feed down', 'chn.feedErr': 'feed down — {e}', 'chn.heardWithin': 'Heard within ~{km} km of you.', 'chn.bands': 'Bands to list:', 'chn.bandsAll': 'All', 'chn.bandsHint': 'Which bands the panel lists — not what this station can work, which is Modes & bands and still applies underneath. A band switched off here is dropped as it arrives, so it stops taking a place in the list.', 'chn.nearKmHint': "— how far a receiver may be and still count as near you. Shared with the band-opening watch.", 'chn.option': 'Chase new (PSK Reporter)', 'chn.optionHelp': 'Lists stations being decoded near you that are new against your log — new entity, band, mode, slot, prefix or square. Digital modes only.', 'chn.show': 'Chase new panel', 'clg2.allFiltered': '{n} spots received, none shown — your filters are hiding them all.', 'clg2.activeFilters': 'Active:', 'clg2.clearAllFilters': 'Clear every filter', 'clg2.fBandLock': 'band locked to the rig', 'clg2.fBands': 'bands {list}', 'clg2.fModeLock': 'mode locked to the rig', 'clg2.fModes': 'modes {list}', 'clg2.fStatus': 'status chips', 'clg2.fHideWorked': 'hide worked', 'clg2.fLotwOnly': 'LoTW users only', 'clg2.fSpotterCont': 'spotter continent', 'clg2.fSource': 'one source node', 'clg2.fSearch': 'search “{q}”', 'clg2.c.time': 'Time', 'clg2.c.call': 'Call', 'clg2.c.status': 'Status', 'clg2.c.pota': 'POTA', 'clg2.c.sota': 'SOTA', 'clg2.c.freq': 'Freq', 'clg2.c.band': 'Band', 'clg2.c.mode': 'Mode', 'clg2.c.pfx': 'Pfx', 'clg2.c.cqz': 'CQ Zone', 'clg2.h.cqz': 'CQZ', 'clg2.c.ituz': 'ITU Zone', 'clg2.h.ituz': 'ITU', 'clg2.c.distance_km': 'Distance (km)', 'clg2.h.distance_km': 'Dist km', 'clg2.c.sp_deg': 'Short path (°)', 'clg2.h.sp_deg': 'SP°', 'clg2.c.lp_deg': 'Long path (°)', 'clg2.h.lp_deg': 'LP°', 'clg2.c.country': 'Country', 'clg2.c.continent': 'Continent', 'clg2.h.continent': 'Cont', 'clg2.c.spotter': 'Spotter', 'clg2.c.source': 'Source', 'clg2.c.locator': 'Spotter locator', 'clg2.h.locator': 'Spotter loc', 'clg2.c.county': 'US County', 'clg2.tipNewCounty': 'NEW COUNTY — never worked', 'clg2.tipNewPfx': 'NEW PREFIX — this WPX prefix has never been worked', 'clg2.c.comment': 'Comment', 'clg2.c.received_at': 'Received at', 'clg2.h.received_at': 'Received UTC', 'clg2.c.raw': 'Raw', 'clg2.newDxcc': 'NEW DXCC', 'clg2.newBandMode': 'NEW B+M', 'clg2.newBand': 'NEW BAND', 'clg2.newMode': 'NEW MODE', 'clg2.newSlot': 'NEW SLOT', 'clg2.newCall': 'NEW CALL', 'clg2.wkdCall': 'WKD CALL', 'clg2.newCounty': 'NEW CTY', 'clg2.newGridUnconf': 'GRID?', 'clg2.newState': 'New State', 'clg2.newGrid': 'NEW GRID', 'clg2.c.grid': 'Grid', 'clg2.tipNewGrid': 'NEW GRID — this square has never been worked (grid heard in a CQ on the UDP link)', 'clg2.newPfx': "NEW PFX", 'clg2.newPota': 'NEW POTA', 'clg2.tipNewDxcc': 'NEW DXCC: {country}', 'clg2.tipWorkedCall': 'Already worked this call', 'clg2.tipNewBandMode': 'NEW BAND AND NEW MODE for this entity — neither has been worked with it', 'clg2.tipNewBand': 'NEW BAND for this entity', 'clg2.tipNewSlotBand': 'NEW SLOT (mode not yet worked on this band)', 'clg2.tipNewMode': 'NEW MODE (this mode never worked on this entity)', 'clg2.tipNewSlot': 'NEW SLOT (this band+mode not yet worked)', 'clg2.tipNewCall': 'NEW CALL — this callsign has never been worked on this band and mode (the entity has)', 'clg2.tipPota': 'POTA — {name}', 'clg2.grpSpot': 'Spot', 'clg2.grpGeo': 'Geo', 'clg2.clearFiltersTitle': 'Clear all column filters', 'clg2.clearFilters': 'Clear filters', 'clg2.newSpots': '{n} new spots — click to resume', 'clg2.columns': 'Columns', 'clg2.pickerTitle': 'Cluster columns', 'clg2.pickerDesc': 'Pick the columns you want visible in the Cluster table.', 'clg2.allGroups': 'All groups:', 'clg2.all': 'all', 'clg2.none': 'none', 'clg2.resetDefaults': 'Reset to defaults', 'clg2.done': 'Done', // Audio devices & voice keyer (Preferences → Audio devices). @@ -721,12 +721,12 @@ const fr: Dict = { // Panneau PSK Reporter à côté des décodages FTx — « est-ce qu'il m'entend ? » 'psk.title': 'PSK Reporter', 'psk.show': "Afficher l'analyse PSK Reporter", 'psk.hide': "Masquer l'analyse PSK Reporter", 'psk.online': 'en ligne', 'psk.offline': 'hors ligne', 'psk.off': 'désactivé', - 'psk.spots': '{n} reports', 'psk.spotsTip': 'Reports reçus sur cette station depuis 5 minutes — le flux vit, même si les compteurs ci-dessous sont à zéro.', + 'psk.spots': '{n} reports', 'psk.spotsTip': 'Reports reçus sur cette station depuis 10 minutes — le flux vit, même si les compteurs ci-dessous sont à zéro.', 'psk.enableHint': "Activez l'analyse PSK Reporter dans Réglages → Décodages FTx pour savoir si la station que vous appelez vous entend.", 'psk.pickHint': 'Cliquez un décodage pour analyser le chemin vers cette station.', 'psk.heardYou': 'Il vous a décodé il y a {s} s', 'psk.pathOpen': 'Chemin ouvert depuis votre région', 'psk.pathOpenSub': 'il a décodé {n} station(s) près de chez vous', 'psk.notYet': 'Pas encore décodé', - 'psk.notYetSub': 'Aucune confirmation de chemin depuis 5 min', + 'psk.notYetSub': 'Aucune confirmation de chemin depuis 10 min', 'psk.nearHim': 'Votre signal reporté près de lui ({g})', 'psk.nRx': '{n} récepteur(s)', 'psk.tFromArea': 'De votre région', 'psk.tFromAreaFoot': "décodées par lui", 'psk.tPileup': 'Pile-up', 'psk.tPileupFoot': 'actif (2 min)', @@ -736,7 +736,7 @@ const fr: Dict = { 'psk.tCallers': 'Appelants entendus', 'psk.tCallersFoot': 'votre concurrence', 'psk.tCallersFootConf': "dont décodés par lui", 'psk.tCallersTip': "Stations de vos propres décodages qui l'appellent. Le nombre entre parenthèses est celui qu'il a décodé — ce sont elles qui lui parviennent vraiment.", 'psk.uploads': 'Il alimente PSK Reporter', 'psk.noUploads': "{c} n'alimente pas PSK Reporter", - 'psk.fromAreaList': 'De votre région — ses reports récents', 'psk.fromAreaEmpty': "Il n'a décodé personne près de chez vous depuis 5 min.", + 'psk.fromAreaList': 'De votre région — ses reports récents', 'psk.fromAreaEmpty': "Il n'a décodé personne près de chez vous depuis 10 min.", 'psk.rowTip': "{c} ({g}) — décodée par lui il y a {s} s à {d} dB. Le décalage est l'endroit où le signal est tombé dans SON passe-bande, pas forcément là où elle appelait.", 'psk.passband': 'Passe-bande (Hz audio)', 'psk.ceiling': 'plafond ≈ {hz} Hz · {n} décodages / 10 min', 'psk.noDial': 'pas de décalage (VFO inconnu)', 'psk.noDecodes': 'aucun décodage', 'psk.tryOffset': 'Essayez +{hz} Hz', @@ -747,10 +747,10 @@ const fr: Dict = { // Appel automatique — le moteur qui répond seul à un décodage (internal/autocall) 'ac.enable': 'Appel automatique', 'ac.enableHint': 'répondre à la meilleure station sur l\'air sans cliquer', 'ac.warn': "Ceci met votre émetteur en marche sans vous demander. Une station à la fois, jamais par-dessus un QSO en cours, et il renonce de lui-même — 7 appels, ou 15 pour un indicatif de la liste de surveillance, et 3 périodes d'émission de la station sans la décoder. Chaque décision est écrite dans le journal avec sa raison, et Stop l'interrompt.", - 'ac.ladder': "Ordre de priorité : nouvelle entité surveillée, nouvelle entité, nouvelle bande surveillée, nouvelle bande, nouveau mode surveillé, nouveau mode, nouveau slot surveillé, nouveau slot, puis un indicatif surveillé dont on n'a plus besoin. Une station en plein QSO avec quelqu'un d'autre n'est jamais appelée — elle ne peut pas répondre.", + 'ac.ladder': "Ordre de priorité : d'abord tout indicatif de la liste de surveillance (entre eux : nouvelle entité, bande, mode, slot), puis nouvelle entité, nouvelle bande, nouveau mode, nouveau slot. Un vrai besoin passe devant un besoin qui n'existe que faute de QSL. Une station qui VOUS appelle est toujours prise, quoi qu'en dise le log. Une station en plein QSO avec quelqu'un d'autre n'est jamais appelée — elle ne peut pas répondre.", 'ac.only': 'Chasser uniquement', 'ac.onlyPh': 'VP6D 3Y0J (espace ou virgule)', 'ac.onlyHint': 'un ou plusieurs indicatifs — rien d’autre n’est appelé ; vide = appeler ce dont le log a besoin', 'ac.attempts': 'Appels avant abandon', 'ac.watchedAttempts': 'si surveillé', 'ac.misses': 'Périodes manquées', - 'ac.rounds': 'Séries par station', 'ac.rest': 'Repos (min)', + 'ac.rounds': 'Séries par station', 'ac.rest': 'Repos (min)', 'ac.trace': 'Journaliser chaque décision', 'ac.traceHint': '— une ligne par période dans le journal de diagnostic : ce qu’il y avait sur l’air, pourquoi chaque station a été écartée, et ce qui a été décidé. Pour comprendre pourquoi rien n’est appelé. Une ligne toutes les quinze secondes : à laisser éteint le reste du temps.', 'ac.onScreen': 'N’appeler que ce qu’affiche la liste des décodages', 'ac.onScreenHint': '— les filtres au-dessus de la liste (CQ seuls, LoTW, les pastilles de catégorie, continents, report, recherche) pilotent aussi l’émetteur : une station filtrée hors écran n’est pas appelée. La liste fermée, rien n’est filtré.', 'dec.chasePh': 'chasser…', 'dec.chaseTip': "N'appeler QUE ces stations — un ou plusieurs indicatifs, espaces ou virgules. Vide = appeler ce dont le log a besoin. Même champ que Réglages → DXHunter.", 'dec.autoStoppedTip': "L'appel automatique a renoncé à une station de la liste de chasse — cliquez pour l'éteindre, ou Stop pour effacer.", // Watchlist contest (Réglages → DXHunter) : comment une flotte d’événement rejoint la liste toute seule 'wlc.title': 'Contest', 'wlc.pattern': 'Ajout auto sur', 'wlc.patternPh': 'WWA', 'wlc.patternHint': 'tout indicatif spotté CONTENANT ceci rejoint la watchlist comme entrée contest (TM29WWA, HB9WWA, F4WWA/P)', @@ -770,7 +770,7 @@ const fr: Dict = { 'dec.colFreq': 'Freq', 'dec.colFreqTitle': 'Decalage audio dans la bande passante (Hz)', 'dec.txNow': 'En emission', 'dec.txIdle': 'Emission', 'dec.working': 'appelle', 'dec.toYou': 'pour toi', 'dec.txUnknown': 'en émission — texte non communiqué', 'dec.txNothing': 'rien en cours d’émission', - 'dec.colTime': 'Heure', 'dec.colSnr': 'SNR', 'dec.colMsg': 'Message', 'dec.bgGridUnconf': 'GRID?', 'dec.bgGridUnconfTip': 'Ce carré est contacté mais pas encore confirmé — une QSL à relancer, pas un QSO à faire.', 'dec.colGrid': 'Locator', 'dec.colState': 'État', 'dec.colCountry': 'Pays', 'dec.colBand': 'Bande', 'dec.colMode': 'Mode', 'dec.colStatus': 'Statut', 'dec.stateTip': 'État US', 'dec.wkd': 'Fait', + 'dec.colTime': 'Heure', 'dec.colRx': 'RX', 'dec.colSnr': 'SNR', 'dec.colMsg': 'Message', 'dec.bgGridUnconf': 'GRID?', 'dec.bgGridUnconfTip': 'Ce carré est contacté mais pas encore confirmé — une QSL à relancer, pas un QSO à faire.', 'dec.colGrid': 'Locator', 'dec.colState': 'État', 'dec.colCountry': 'Pays', 'dec.colBand': 'Bande', 'dec.colMode': 'Mode', 'dec.colStatus': 'Statut', 'dec.stateTip': 'État US', 'dec.wl': 'WL', 'dec.wlTip': 'Sur votre liste de surveillance', 'dec.wkd': 'Fait', 'dec.bgPota': 'POTA', 'dec.bgGrid': 'LOC', 'dec.bgPfx': 'PFX', 'dec.bgState': 'Nouvel État', 'dec.bgCounty': 'CTY', 'dec.stNew': 'NOUV', 'dec.stBand': 'BANDE', 'dec.stMode': 'MODE', 'dec.stSlot': 'SLOT', 'dec.stCall': 'IND', 'dec.empty': "Aucun decode pour l'instant. Ils arrivent de WSJT-X, JTDX ou MSHV par le lien UDP entrant (Reglages -> UDP).", @@ -1095,7 +1095,7 @@ const fr: Dict = { 'qedit.qslDash': '—', 'qedit.qslYes': 'Oui', 'qedit.qslVerified': 'Vérifié', 'qedit.qslNo': 'Non', 'qedit.qslRequested': 'Demandé', 'qedit.qslIgnore': 'Ignorer', 'qedit.statusModified': 'Modifié', 'qedit.confOpsLog': 'Carte OpsLog', 'qedit.opslogSentHint': "« Envoyée » est inscrit par OpsLog quand la carte part réellement — cela ne se modifie pas ici.", 'qedit.confQslPaper': 'QSL (papier)', 'qedit.callsignRequired': 'Indicatif requis', 'qedit.lookupError': 'Recherche : {msg}', 'qedit.title': 'Modifier le QSO', 'qedit.editFieldsFor': 'Modifier les champs du QSO #{id}', 'qedit.tabQsoInfo': 'Infos QSO', 'qedit.tabContact': 'Détails du contact', 'qedit.tabAwards': 'Réf. diplômes', 'qedit.tabQsl': 'Infos QSL', 'qedit.tabContest': 'Concours', 'qedit.tabSat': 'Sat / Prop', 'qedit.tabMyStation': 'Ma station', 'qedit.tabMoreAdif': "Plus d'ADIF", 'qedit.tabAdifFields': 'Champs ADIF', 'qedit.callsign': 'Indicatif', 'qedit.fetchTitle': 'Rechercher cet indicatif (QRZ.com / HamQTH) et actualiser nom, pays, locator, zones…', 'qedit.fetch': 'Rechercher', 'qedit.name': 'Nom', 'qedit.band': 'Bande', 'qedit.rxBand': 'Bande RX', 'qedit.mode': 'Mode', 'qedit.country': 'Pays', 'qedit.dxccTitle': "Entité DXCC n° — définie automatiquement d'après le pays", 'qedit.txFreq': 'Fréq. TX', 'qedit.rxFreq': 'Fréq. RX', 'qedit.qsoStart': 'Début QSO (UTC)', 'qedit.qsoEnd': 'Fin QSO (UTC)', 'qedit.grid': 'Locator', 'qedit.comment': 'Commentaire', 'qedit.note': 'Note', 'qedit.county': 'Comté', 'qedit.state': 'État', 'qedit.continent': 'Continent', 'qedit.address': 'Adresse', 'qedit.email': 'Adresse e-mail', 'qedit.qslMsg': 'Message QSL', 'qedit.qslReceived': 'QSL OpsLog reçue', 'qedit.pseTnxHint': 'Tampon carte : TNX QSL si une QSL a été reçue, sinon PSE QSL', 'qedit.qslVia': 'QSL via (manager)', 'qedit.computedAuto': 'Calculé (automatique)', 'qedit.computedHint': 'Dérivé des champs de ce QSO (DXCC, zones, préfixe, notes…). Non modifiable ici.', 'qedit.noneYet': "Aucun pour l'instant.", 'qedit.manageConf': 'Gérer la confirmation', 'qedit.sent': 'Envoyé', 'qedit.received': 'Reçu', 'qedit.dateSent': "Date d'envoi", 'qedit.dateReceived': 'Date de réception', 'qedit.sentVia': 'Envoyée via', 'qedit.rcvdVia': 'Reçue via', 'qedit.viaBureau': 'Bureau', 'qedit.viaDirect': 'Direct', 'qedit.viaElectronic': 'Électronique (OQRS)', 'qedit.qslViaPlaceholder': 'indicatif du manager', 'qedit.qslPanelHint': 'Choisissez un canal, modifiez-le — le tableau de droite se met à jour en direct. Tout est enregistré quand vous cliquez sur', 'qedit.saveChanges': 'Enregistrer', 'qedit.thType': 'Type', 'qedit.contestId': 'ID concours', 'qedit.rcvdExchange': 'échange reçu', 'qedit.sentExchange': 'échange envoyé', 'qedit.check': 'Check', 'qedit.precedence': 'Précédence', 'qedit.arrlSection': 'Section ARRL', 'qedit.propMode': 'Mode de propagation', 'qedit.satName': 'Nom du satellite', 'qedit.satMode': 'Mode satellite', 'qedit.antAz': 'Azimut antenne (°)', 'qedit.antEl': 'Élévation antenne (°)', 'qedit.antPath': "Chemin d'antenne", 'qedit.myStationHint': 'Ces valeurs remplacent le profil de station actif pour ce QSO uniquement.', 'qedit.stationCallsign': 'Indicatif de la station', 'qedit.operator': 'Opérateur', 'qedit.myGrid': 'Mon locator', 'qedit.myGridExt': 'Mon loc. étendu', 'qedit.gridExtTip': 'Caractères 9 à 12 du locator étendu (ADIF 3.1.4) — plus précis que les 8 du QRA habituel', 'qedit.gridExt': 'Ext. locator', 'qedit.cqZone': 'Zone CQ', 'qedit.ituZone': 'Zone ITU', 'qedit.sotaRef': 'Réf. SOTA', 'qedit.potaRef': 'Réf. POTA', 'qedit.street': 'Rue', 'qedit.city': 'Ville', 'qedit.postal': 'Code postal', 'qedit.rig': 'Équipement', 'qedit.antenna': 'Antenne', 'qedit.specialActivity': 'Activité spéciale', 'qedit.sigInfo': 'Info SIG', 'qedit.wwffRef': 'Réf. WWFF', 'qedit.region': 'Région', 'qedit.powerWeather': 'Puissance et météo spatiale', 'qedit.txPower': 'Ma puissance (W)', 'qedit.rxPower': 'Sa puissance (W)', 'qedit.distance': 'Distance (km)', 'qedit.aIndex': 'Indice A', 'qedit.kIndex': 'Indice K', 'qedit.identityClubs': 'Identité et clubs', 'qedit.contactedOp': 'Opérateur contacté', 'qedit.formerCall': 'Ancien indicatif (EQ_CALL)', 'qedit.class': 'Classe', 'qedit.flagsCredits': 'Indicateurs et crédits', 'qedit.qsoComplete': 'QSO complet', 'qedit.qsoRandom': 'QSO aléatoire', 'qedit.silentKey': 'Silent key', 'qedit.creditGranted': 'Crédit accordé', 'qedit.creditSubmitted': 'Crédit soumis', 'qedit.myStationAdif': 'Ma station (ADIF)', 'qedit.myName': 'Mon nom', 'qedit.myWwffRef': 'Ma réf. WWFF', 'qedit.myArrlSect': 'Ma section ARRL', 'qedit.mySig': 'Mon SIG', 'qedit.mySigInfo': 'Mon info SIG', 'qedit.myDarcDok': 'Mon DARC DOK', 'qedit.myVuccGrids': 'Mes locators VUCC', 'qedit.delete': 'Supprimer', 'qedit.cancel': 'Annuler', 'qedit.saving': 'Enregistrement…', 'rqg.c.number': 'Numéro de QSO', 'rqg.h.number': 'N°', 'rqg.c.qso_date': 'Date UTC', 'rqg.c.qso_date_off': 'Date fin', 'rqg.c.callsign': 'Indicatif', 'rqg.c.band': 'Bande', 'rqg.c.band_rx': 'Bande RX', 'rqg.c.mode': 'Mode', 'rqg.c.submode': 'Sous-mode', 'rqg.c.freq_hz': 'Fréq (TX)', 'rqg.h.freq_hz': 'Fréq', 'rqg.c.freq_rx_hz': 'Fréq (RX)', 'rqg.h.freq_rx_hz': 'Fréq RX', 'rqg.c.rst_sent': 'RST env', 'rqg.c.rst_rcvd': 'RST reçu', 'rqg.c.tx_pwr': 'Puiss. TX', 'rqg.c.name': 'Nom', 'rqg.c.qth': 'QTH', 'rqg.c.address': 'Adresse', 'rqg.c.country': 'Pays', 'rqg.c.state': 'État', 'rqg.c.cnty': 'Comté', 'rqg.c.cont': 'Continent', 'rqg.h.cont': 'Cont', 'rqg.c.grid': 'Locator', 'rqg.c.gridsquare_ext': 'Ext. loc.', 'rqg.h.gridsquare_ext': 'ExtLoc', 'rqg.c.vucc_grids': 'Locators VUCC', 'rqg.h.vucc_grids': 'VUCC', 'rqg.c.dxcc': 'DXCC #', 'rqg.c.cqz': 'CQZ', 'rqg.c.ituz': 'ITU', 'rqg.c.iota': 'IOTA', 'rqg.c.sota_ref': 'Réf. SOTA', 'rqg.h.sota_ref': 'SOTA', 'rqg.c.pota_ref': 'Réf. POTA', 'rqg.h.pota_ref': 'POTA', 'rqg.c.age': 'Âge', 'rqg.c.lat': 'Lat', 'rqg.c.lon': 'Lon', 'rqg.c.distance_km': 'Distance (km)', 'rqg.h.distance_km': 'Dist km', 'rqg.c.email': 'E-mail', 'rqg.c.web': 'Web', 'rqg.c.qsl_sent': 'QSL env', 'rqg.c.qsl_rcvd': 'QSL reçu', 'rqg.c.qsl_sent_date': 'Date env QSL', 'rqg.h.qsl_sent_date': 'QSL env.', 'rqg.c.qsl_rcvd_date': 'Date reçu QSL', 'rqg.h.qsl_rcvd_date': 'QSL reçu', 'rqg.c.qsl_via': 'QSL via (manager)', 'rqg.c.qsl_sent_via': 'QSL envoyée via', 'rqg.h.qsl_sent_via': 'Env. via', 'rqg.c.qsl_rcvd_via': 'QSL reçue via', 'rqg.h.qsl_rcvd_via': 'Reçue via', 'rqg.c.qsl_msg': 'Msg QSL', 'rqg.c.qslmsg_rcvd': 'Msg QSL reçu', 'rqg.c.lotw_sent': 'LoTW env', 'rqg.c.lotw_rcvd': 'LoTW reçu', 'rqg.c.lotw_sent_date': 'Date env LoTW', 'rqg.h.lotw_sent_date': 'LoTW env.', 'rqg.c.lotw_rcvd_date': 'Date reçu LoTW', 'rqg.h.lotw_rcvd_date': 'LoTW reçu', 'rqg.c.eqsl_sent': 'eQSL env', 'rqg.c.eqsl_rcvd': 'eQSL reçu', 'rqg.c.eqsl_sent_date': 'Date env eQSL', 'rqg.h.eqsl_sent_date': 'eQSL env.', 'rqg.c.eqsl_rcvd_date': 'Date reçu eQSL', 'rqg.h.eqsl_rcvd_date': 'eQSL reçu', 'rqg.c.opslog_qsl_card_sent': 'QSL OpsLog', 'rqg.c.hamlog_sent': 'HAMLOG.online envoyé', 'rqg.h.hamlog_sent': 'HAMLOG env.', 'rqg.c.hamlog_sent_date': "HAMLOG.online date d'envoi", 'rqg.h.hamlog_sent_date': 'HAMLOG date env.', 'rqg.c.hamlog_rcvd': 'HAMLOG.online reçu', 'rqg.h.hamlog_rcvd': 'HAMLOG reçu', 'rqg.c.hamlog_rcvd_date': 'HAMLOG.online date de réception', 'rqg.h.hamlog_rcvd_date': 'HAMLOG date reçu', 'rqg.c.opslog_qsl_card_rcvd': 'QSL OpsLog reçue', 'rqg.c.opslog_recording_sent': 'Enreg. envoyé', 'rqg.h.opslog_recording_sent': 'Enr. env', 'rqg.c.clublog_sent': 'ClubLog env', 'rqg.c.clublog_sent_date': 'Date env ClubLog', 'rqg.h.clublog_sent_date': 'ClubLog env.', 'rqg.c.clublog_rcvd': 'Match ClubLog', 'rqg.c.clublog_rcvd_date': 'Date match ClubLog', 'rqg.h.clublog_rcvd_date': 'ClubLog match', 'rqg.c.hrdlog_sent': 'HRDLog env', 'rqg.c.hrdlog_sent_date': 'Date env HRDLog', 'rqg.h.hrdlog_sent_date': 'HRDLog env.', 'rqg.c.qrz_sent': 'QRZ.com env', 'rqg.c.qrz_rcvd': 'QRZ.com reçu', 'rqg.c.qrz_sent_date': 'Date env QRZ.com', 'rqg.h.qrz_sent_date': 'QRZ.com env.', 'rqg.c.qrz_rcvd_date': 'Date reçu QRZ.com', 'rqg.h.qrz_rcvd_date': 'QRZ.com reçu', 'rqg.c.contest_id': 'ID concours', 'rqg.h.contest_id': 'Concours', 'rqg.c.srx': 'SRX', 'rqg.c.stx': 'STX', 'rqg.c.srx_string': 'Chaîne SRX', 'rqg.h.srx_string': 'SRX str', 'rqg.c.stx_string': 'Chaîne STX', 'rqg.h.stx_string': 'STX str', 'rqg.c.check': 'Check', 'rqg.c.precedence': 'Précédence', 'rqg.c.arrl_sect': 'Section ARRL', 'rqg.h.arrl_sect': 'Sect. ARRL', 'rqg.c.prop_mode': 'Mode prop.', 'rqg.h.prop_mode': 'Prop', 'rqg.c.sat_name': 'Nom sat.', 'rqg.h.sat_name': 'Sat', 'rqg.c.sat_mode': 'Mode sat.', 'rqg.c.ant_az': 'Azimut ant.', 'rqg.h.ant_az': 'Az', 'rqg.c.ant_el': 'Élévation ant.', 'rqg.h.ant_el': 'Él', 'rqg.c.ant_path': 'Chemin ant.', 'rqg.h.ant_path': 'Chemin', 'rqg.c.station_callsign': 'Indicatif station', 'rqg.h.station_callsign': 'Station', 'rqg.c.operator': 'Opérateur', 'rqg.c.my_grid': 'Mon locator', 'rqg.c.my_country': 'Mon pays', 'rqg.h.my_country': 'Mon pays', 'rqg.c.my_state': 'Mon état', 'rqg.c.my_cnty': 'Mon comté', 'rqg.h.my_cnty': 'Mon comté', 'rqg.c.my_iota': 'Mon IOTA', 'rqg.c.my_sota': 'Mon SOTA', 'rqg.c.my_pota': 'Mon POTA', 'rqg.c.my_dxcc': 'Mon DXCC', 'rqg.h.my_dxcc': 'Mon DXCC#', 'rqg.c.my_cq_zone': 'Ma zone CQ', 'rqg.h.my_cq_zone': 'Ma CQZ', 'rqg.c.my_itu_zone': 'Ma zone ITU', 'rqg.h.my_itu_zone': 'Ma ITU', 'rqg.c.my_lat': 'Ma lat', 'rqg.c.my_lon': 'Ma lon', 'rqg.c.my_street': 'Ma rue', 'rqg.h.my_street': 'Rue', 'rqg.c.my_city': 'Ma ville', 'rqg.h.my_city': 'Ville', 'rqg.c.my_zip': 'Mon code postal', 'rqg.h.my_zip': 'CP', 'rqg.c.my_rig': 'Mon équipement', 'rqg.c.my_antenna': 'Mon antenne', 'rqg.c.my_name': 'Mon nom', 'rqg.c.my_wwff': 'Mon WWFF', 'rqg.c.my_sig': 'Mon SIG', 'rqg.c.my_sig_info': 'Mon info SIG', 'rqg.c.my_arrl_sect': 'Ma section ARRL', 'rqg.c.my_darc_dok': 'Mon DARC DOK', 'rqg.c.my_vucc_grids': 'Mes carrés VUCC', 'rqg.h.my_antenna': 'Mon ant.', 'rqg.c.comment': 'Commentaire', 'rqg.c.notes': 'Notes', 'rqg.c.created': 'Créé', 'rqg.h.created': 'Créé le', 'rqg.c.updated': 'Mis à jour', 'rqg.h.updated': 'Mis à jour le', 'rqg.grpQso': 'QSO', 'rqg.grpContacted': 'Station contactée', 'rqg.grpQsl': 'QSL', 'rqg.grpLotw': 'LoTW', 'rqg.grpEqsl': 'eQSL', 'rqg.grpUploads': 'Envois', 'rqg.grpContest': 'Concours', 'rqg.grpProp': 'Propagation', 'rqg.grpMyStation': 'Ma station', 'rqg.grpMisc': 'Divers', 'rqg.grpAwards': 'Diplômes', 'rqg.awardTip': '{name} — référence comptée pour ce QSO', 'rqg.clearFiltersTitle': 'Effacer tous les filtres de colonne', 'rqg.clearFilters': 'Effacer les filtres', 'rqg.selectedCount': '{n} sélectionné(s)', 'rqg.selectAll': 'Tout sélectionner', 'rqg.selectAllTitle': 'Sélectionner toutes les lignes affichées (respecte les filtres de colonnes actifs)', 'rqg.unselectAll': 'Tout désélectionner', 'rqg.unselectAllTitle': 'Effacer la sélection', 'rqg.columns': 'Colonnes', 'rqg.pickerDesc': 'Choisissez les colonnes à afficher dans le tableau des QSO récents. Votre sélection est enregistrée.', 'rqg.pickerAllGroups': 'Tous les groupes :', 'rqg.all': 'tout', 'rqg.none': 'aucun', 'rqg.resetDefaults': 'Réinitialiser', 'rqg.done': 'Terminé', 'wbg.awardTip': '{name} — référence comptée pour ce QSO', 'wbg.typeCall': 'Saisissez un indicatif dans la barre pour voir les contacts précédents.', 'wbg.checking': 'vérification…', 'wbg.new': 'NOUVEAU', 'wbg.noPriorPre': 'Aucun QSO précédent avec ', 'wbg.noPriorPost': '.', 'wbg.workedBefore': 'Déjà contacté', 'wbg.first': 'Premier :', 'wbg.last': 'Dernier :', 'wbg.dxcc': 'DXCC :', 'wbg.entityQsos': '{n} QSO avec cette entité', 'wbg.clearFiltersTitle': 'Effacer tous les filtres de colonne', 'wbg.clearFilters': 'Effacer les filtres', 'wbg.columns': 'Colonnes', 'wbg.olderQsos': '+ {n} QSO plus anciens (non affichés — limités pour la performance)', 'wbg.pickerTitle': 'Colonnes « Déjà contacté »', 'wbg.pickerDesc': 'Choisissez les colonnes à afficher dans le tableau « Déjà contacté ».', 'wbg.allGroups': 'Tous les groupes :', 'wbg.all': 'tout', 'wbg.none': 'aucun', 'wbg.grpAwards': 'Diplômes', 'wbg.resetDefaults': 'Réinitialiser', 'wbg.done': 'Terminé', - 'chn.title': 'Chasse au nouveau', 'chn.close': 'Masquer le panneau', 'chn.filterHint': 'Afficher ou masquer ce type', 'chn.allFiltered': 'Tout ce qui est entendu est filtré — réactivez une catégorie ci-dessus.', 'chn.toggle': 'Chasse au nouveau', 'chn.count': '{n} entendus', 'chn.loading': 'En attente du flux…', 'chn.empty': 'Rien de nouveau décodé près de vous pour le moment.', 'chn.digitalOnly': 'PSK Reporter — modes numériques uniquement, entendus à moins de ~300 km.', 'chn.nearKmHint': "— distance maximale d'un récepteur pour compter comme proche de vous. Partagé avec la veille d'ouvertures.", 'chn.option': 'Chasse au nouveau (PSK Reporter)', 'chn.optionHelp': 'Liste les stations décodées près de chez vous qui sont nouvelles par rapport à votre log — entité, bande, mode, créneau, préfixe ou carré. Modes numériques uniquement.', 'chn.show': 'Panneau chasse au nouveau', + 'chn.title': 'Chasse au nouveau', 'chn.close': 'Masquer le panneau', 'chn.filterHint': 'Afficher ou masquer ce type', 'chn.allFiltered': 'Tout ce qui est entendu est filtré — réactivez une catégorie ci-dessus.', 'chn.toggle': 'Chasse au nouveau', 'chn.loading': 'En attente du flux…', 'chn.empty': 'Rien de nouveau décodé près de vous pour le moment.', 'chn.count': '{n} entendus', 'chn.countOf': '{n} sur {total} entendus', 'chn.feedOn': 'flux actif · {n} reports · {sq} carrés', 'chn.feedOff': 'flux coupé', 'chn.feedErr': 'flux coupé — {e}', 'chn.heardWithin': 'Entendus à moins de ~{km} km de chez vous.', 'chn.bands': 'Bandes à lister :', 'chn.bandsAll': 'Toutes', 'chn.bandsHint': 'Les bandes que le panneau liste — pas ce que la station sait travailler, qui reste Modes & bandes et s’applique en dessous. Une bande décochée ici est écartée à l’arrivée : elle cesse de prendre une place dans la liste.', 'chn.nearKmHint': "— distance maximale d'un récepteur pour compter comme proche de vous. Partagé avec la veille d'ouvertures.", 'chn.option': 'Chasse au nouveau (PSK Reporter)', 'chn.optionHelp': 'Liste les stations décodées près de chez vous qui sont nouvelles par rapport à votre log — entité, bande, mode, créneau, préfixe ou carré. Modes numériques uniquement.', 'chn.show': 'Panneau chasse au nouveau', 'clg2.allFiltered': '{n} spots reçus, aucun affiché — vos filtres les masquent tous.', 'clg2.activeFilters': 'Actifs :', 'clg2.clearAllFilters': 'Effacer tous les filtres', 'clg2.fBandLock': 'bande verrouillée sur la radio', 'clg2.fBands': 'bandes {list}', 'clg2.fModeLock': 'mode verrouillé sur la radio', 'clg2.fModes': 'modes {list}', 'clg2.fStatus': 'pastilles de statut', 'clg2.fHideWorked': 'masquer les contactés', 'clg2.fLotwOnly': 'utilisateurs LoTW uniquement', 'clg2.fSpotterCont': 'continent du spotteur', 'clg2.fSource': 'un seul nœud source', 'clg2.fSearch': 'recherche « {q} »', 'clg2.c.time': 'Heure', 'clg2.c.call': 'Indicatif', 'clg2.c.status': 'Statut', 'clg2.c.pota': 'POTA', 'clg2.c.sota': 'SOTA', 'clg2.c.freq': 'Fréq', 'clg2.c.band': 'Bande', 'clg2.c.mode': 'Mode', 'clg2.c.pfx': 'Préf.', 'clg2.c.cqz': 'Zone CQ', 'clg2.h.cqz': 'CQZ', 'clg2.c.ituz': 'Zone ITU', 'clg2.h.ituz': 'ITU', 'clg2.c.distance_km': 'Distance (km)', 'clg2.h.distance_km': 'Dist km', 'clg2.c.sp_deg': 'Chemin court (°)', 'clg2.h.sp_deg': 'CC°', 'clg2.c.lp_deg': 'Chemin long (°)', 'clg2.h.lp_deg': 'CL°', 'clg2.c.country': 'Pays', 'clg2.c.continent': 'Continent', 'clg2.h.continent': 'Cont', 'clg2.c.spotter': 'Spotter', 'clg2.c.source': 'Source', 'clg2.c.locator': 'Locator du spotter', 'clg2.h.locator': 'Loc spotter', 'clg2.c.county': 'Comté US', 'clg2.tipNewCounty': 'NOUVEAU COMTÉ — jamais contacté', 'clg2.tipNewPfx': "NOUVEAU PRÉFIXE — ce préfixe WPX n'a jamais été contacté", 'clg2.c.comment': 'Commentaire', 'clg2.c.received_at': 'Reçu le', 'clg2.h.received_at': 'Reçu UTC', 'clg2.c.raw': 'Brut', 'clg2.newDxcc': 'NOUV DXCC', 'clg2.newBandMode': 'NOUV B+M', 'clg2.newBand': 'NOUV BANDE', 'clg2.newMode': 'NOUV MODE', 'clg2.newSlot': 'NOUV SLOT', 'clg2.newCall': 'CALL NEUF', 'clg2.wkdCall': 'DÉJÀ QSO', 'clg2.newCounty': 'NOUV CTY', 'clg2.newGridUnconf': 'GRID?', 'clg2.newState': 'Nouvel État', 'clg2.newGrid': 'NOUV GRID', 'clg2.c.grid': 'Grid', 'clg2.tipNewGrid': 'NOUVEAU GRID — ce carré n a jamais été contacté (grid entendu dans un CQ sur le lien UDP)', 'clg2.newPfx': "NOUVEAU PFX", 'clg2.newPota': 'NOUV POTA', 'clg2.tipNewDxcc': 'NOUVEAU DXCC : {country}', 'clg2.tipWorkedCall': 'Indicatif déjà contacté', 'clg2.tipNewBandMode': 'NOUVELLE BANDE ET NOUVEAU MODE pour cette entité — aucun des deux n’a été fait avec elle', 'clg2.tipNewBand': 'NOUVELLE BANDE pour cette entité', 'clg2.tipNewSlotBand': 'NOUVEAU SLOT (mode pas encore contacté sur cette bande)', 'clg2.tipNewMode': 'NOUVEAU MODE (ce mode jamais contacté sur cette entité)', 'clg2.tipNewSlot': 'NOUVEAU SLOT (cette bande+mode pas encore contactée)', 'clg2.tipNewCall': "CALL NEUF — cet indicatif n a jamais été contacté sur cette bande et ce mode (l entité, si)", 'clg2.tipPota': 'POTA — {name}', 'clg2.grpSpot': 'Spot', 'clg2.grpGeo': 'Géo', 'clg2.clearFiltersTitle': 'Effacer tous les filtres de colonne', 'clg2.clearFilters': 'Effacer les filtres', 'clg2.newSpots': '{n} nouveaux spots — cliquer pour reprendre', 'clg2.columns': 'Colonnes', 'clg2.pickerTitle': 'Colonnes du cluster', 'clg2.pickerDesc': 'Choisissez les colonnes à afficher dans le tableau du cluster.', 'clg2.allGroups': 'Tous les groupes :', 'clg2.all': 'tout', 'clg2.none': 'aucun', 'clg2.resetDefaults': 'Réinitialiser', 'clg2.done': 'Terminé', // Périphériques audio et manipulateur vocal (Préférences → Périphériques audio). diff --git a/frontend/src/lib/spot.ts b/frontend/src/lib/spot.ts index 534ac64..565468c 100644 --- a/frontend/src/lib/spot.ts +++ b/frontend/src/lib/spot.ts @@ -15,6 +15,12 @@ export function cleanSpotter(s: string): string { // alone instead of guessing wrong. export function inferSpotMode(comment: string, freqHz: number): string { const c = (comment || '').toUpperCase(); + // SuperFox and Fox/Hound are FT8 — they are WSJT-X's DXpedition transmit + // modes, not modes of their own. A spot commented "super fox" fell through to + // the band plan and came out DATA, and that verdict is not cosmetic: the + // band+mode status is computed from this answer, so a ZD8 on 21.071 read as a + // new DATA slot rather than the new FT8 one it is. + if (/\bSUPER\s*FOX\b|\bSFOX\b|\bFOX\s*\/?\s*HOUND\b|\bF\/H\b/.test(c)) return 'FT8'; if (/\bFT8\b/.test(c)) return 'FT8'; if (/\bFT4\b/.test(c)) return 'FT4'; if (/\bJS8\b/.test(c)) return 'JS8';