diff --git a/changelog.json b/changelog.json index 1f2dc16..f2bae48 100644 --- a/changelog.json +++ b/changelog.json @@ -4,11 +4,13 @@ "date": "", "en": [ "When TQSL refuses an upload, the log now carries the exact ADIF record it was given and the station location it was told to sign with. 'No QSOs processed' covers several unrelated causes and the temp file is deleted the moment TQSL returns, so the one piece of evidence that mattered was the one nobody could see.", - "An Elecraft console for the K3/K4, in a tab of its own when the CAT backend is Elecraft or Kenwood: power, volume, RF and mic gain, squelch, preamp, attenuator, NB, NR, AGC, filter width, antenna, RIT/XIT with a clear, keyer speed, S-meter, SWR, MOX and an ATU tune. The meter scaling is marked provisional and the raw readings go to the log — it was written from the K3 Programmer's Reference without a radio to hand, and a wrongly scaled SWR bar reports a good match on a bad antenna." + "An Elecraft console for the K3/K4, in a tab of its own when the CAT backend is Elecraft or Kenwood: power, volume, RF and mic gain, squelch, preamp, attenuator, NB, NR, AGC, filter width, antenna, RIT/XIT with a clear, keyer speed, S-meter, SWR, MOX and an ATU tune. The meter scaling is marked provisional and the raw readings go to the log — it was written from the K3 Programmer's Reference without a radio to hand, and a wrongly scaled SWR bar reports a good match on a bad antenna.", + "Type a number in the callsign field and press Enter to move the radio: kHz for a frequency (28500 → 28.500 MHz), or a band on its own (20, 160, 6…). It is where the hands already are — reading a spot and reaching for the frequency box is what loses the station. Ambiguous numbers are read as bands, since 40 kHz and 160 kHz are nowhere anyone tunes." ], "fr": [ "Quand TQSL refuse un envoi, le journal contient désormais l'enregistrement ADIF exact qui lui a été remis et l'emplacement de station demandé pour la signature. « No QSOs processed » recouvre plusieurs causes sans rapport et le fichier temporaire est supprimé dès que TQSL rend la main : la seule pièce à conviction utile était justement invisible.", - "Une console Elecraft pour les K3/K4, dans un onglet dédié quand le CAT est réglé sur Elecraft ou Kenwood : puissance, volume, gain HF et micro, squelch, préampli, atténuateur, NB, NR, AGC, largeur de filtre, antenne, RIT/XIT avec effacement, vitesse du manipulateur, S-mètre, ROS, MOX et accord de l'ATU. L'échelle des mesures est signalée comme provisoire et les valeurs brutes partent dans le journal — la console a été écrite d'après le manuel de programmation du K3, sans radio sous la main, et une barre de ROS mal calibrée annonce un bon accord sur une mauvaise antenne." + "Une console Elecraft pour les K3/K4, dans un onglet dédié quand le CAT est réglé sur Elecraft ou Kenwood : puissance, volume, gain HF et micro, squelch, préampli, atténuateur, NB, NR, AGC, largeur de filtre, antenne, RIT/XIT avec effacement, vitesse du manipulateur, S-mètre, ROS, MOX et accord de l'ATU. L'échelle des mesures est signalée comme provisoire et les valeurs brutes partent dans le journal — la console a été écrite d'après le manuel de programmation du K3, sans radio sous la main, et une barre de ROS mal calibrée annonce un bon accord sur une mauvaise antenne.", + "Taper un nombre dans le champ indicatif puis Entrée déplace la radio : en kHz pour une fréquence (28500 → 28.500 MHz), ou une bande seule (20, 160, 6…). C'est là que sont déjà les mains — lire un spot puis aller chercher la case fréquence, c'est ce qui fait perdre la station. Les nombres ambigus sont lus comme des bandes : 40 kHz ou 160 kHz ne sont nulle part où l'on s'accorde." ] }, { diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 5b07d40..9abce97 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -292,6 +292,37 @@ function shortCatError(err?: string): string { } // bandForMHz maps a dial frequency (MHz) to its ADIF band, or '' if outside // every known allocation (used to auto-fill the band when the freq changes). +// A number typed into the CALLSIGN field is a QSY, not a callsign. +// +// It is where the hands already are: an operator watching a cluster or a +// spotting page reads "28500" and wants to be there, and reaching for the +// frequency box — or worse, the mouse — is the part that loses the station. So +// the call field takes it, on Enter, and nothing else in the entry form changes. +// +// Two forms, and the AMBIGUOUS ones resolve to the band deliberately. "160" is +// the 160 m band, not 160 kHz; "40" is 40 m, not 40 kHz. Neither number is a +// frequency anyone tunes to, so reading them as bands costs nothing and reads +// the way an operator says it out loud. +const ENTRY_BAND_WORDS: Record = { + '2200': '2200m', '630': '630m', '160': '160m', '80': '80m', '60': '60m', + '40': '40m', '30': '30m', '20': '20m', '17': '17m', '15': '15m', '12': '12m', + '10': '10m', '6': '6m', '4': '4m', '2': '2m', '70': '70cm', +}; + +// entryQSYCommand reads what was typed. Returns null for anything that is not +// unambiguously one of the two — a callsign must never be mistaken for a QSY. +function entryQSYCommand(text: string): { band?: string; hz?: number } | null { + const v = text.trim(); + if (!/^\d{1,7}$/.test(v)) return null; // digits only: a callsign has letters + const band = ENTRY_BAND_WORDS[v]; + if (band) return { band }; + const khz = Number(v); + // 1500 kHz to 1.3 GHz: below that is not a band anyone works, above it is a + // typo. A bare "500" is neither a band nor a frequency, so nothing happens. + if (khz >= 1500 && khz <= 1_300_000) return { hz: Math.round(khz * 1000) }; + return null; +} + function bandForMHz(mhz: number): string { if (!mhz || isNaN(mhz)) return ''; const plan: [number, number, string][] = [ @@ -702,6 +733,7 @@ export default function App() { '15m': { SSB: 21300000, CW: 21030000, FT8: 21074000, FT4: 21140000, RTTY: 21080000, default: 21300000 }, '12m': { SSB: 24950000, CW: 24910000, FT8: 24915000, FT4: 24919000, RTTY: 24920000, default: 24950000 }, '10m': { SSB: 28500000, CW: 28030000, FT8: 28074000, FT4: 28180000, RTTY: 28080000, default: 28500000 }, + '4m': { SSB: 70200000, CW: 70100000, FT8: 70154000, default: 70200000 }, '6m': { SSB: 50150000, CW: 50080000, FT8: 50313000, FT4: 50318000, default: 50150000 }, '2m': { SSB: 144300000, CW: 144050000, FT8: 144174000, default: 144300000 }, '70cm': { SSB: 432300000, CW: 432050000, FT8: 432174000, default: 432300000 }, @@ -5469,6 +5501,25 @@ export default function App() { noteManualEdit(); SetCATFrequency(Math.round(mhz * 1_000_000)).catch(() => {}); }; + // Carry out what was typed in the call field. Bands go through the ordinary + // band change, so the antennas, the power table and the outbound integrations + // all hear about it exactly as they would from the dropdown. + const applyEntryQSY = (q: { band?: string; hz?: number }) => { + if (q.band) { + onBandUserChange(q.band); + showToast(q.band); + return; + } + const hz = q.hz ?? 0; + if (hz <= 0) return; + noteManualEdit(); + setFreqMhz((hz / 1_000_000).toFixed(6)); + const b = bandForMHz(hz / 1_000_000); + if (b) setBand(b); + if (catState.enabled && catState.connected) SetCATFrequency(hz).catch(() => {}); + showToast((hz / 1_000_000).toFixed(3) + ' MHz'); + }; + // Mouse-wheel over a kHz digit of the top frequency readout: step the frequency // and (if CAT is connected) QSY the rig. The display updates optimistically on // every notch; the actual radio tune is debounced so a fast scroll doesn't flood @@ -6835,6 +6886,16 @@ export default function App() { onKeyDown={(e) => { if (e.key === 'Enter' && (e.target as HTMLElement).tagName === 'INPUT') { e.preventDefault(); + // A number in the call field is a QSY. Checked BEFORE ESM and before + // logging: both would otherwise act on a "callsign" of 28500. + if (e.target === callsignRef.current) { + const q = entryQSYCommand(callsignRef.current?.value ?? ''); + if (q) { + applyEntryQSY(q); + setCallsign(''); // the field goes back to what it is for + return; + } + } // ESM (Enter Sends Message): fire the stage-appropriate CW macro instead // of logging. Falls through to the normal log when ESM isn't active. if (esmHandleEnter(e.target as HTMLElement)) return;