Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 7 additions & 3 deletions src/app/new-contact/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ import { Switch } from '@/components/ui/switch';
import { Textarea } from '@/components/ui/textarea';
import { cn } from '@/lib/utils';
import { frequencyToBand, AMATEUR_BANDS } from '@/lib/bands';
import { AMATEUR_MODES, defaultRstForMode } from '@/lib/modes';
import { AMATEUR_MODES, defaultRstForMode, isDbReportMode } from '@/lib/modes';
import {
gridToLatLon,
gridLocatorError,
Expand Down Expand Up @@ -134,8 +134,12 @@ function rstToBars(rst: string | undefined, mode: string): number {
// Digital values like -10 / +12 — map to 5 bars across -25..+10 dB
const numeric = Number.parseInt(rst.replace(/[^-\d]/g, ''), 10);
if (Number.isNaN(numeric)) return 0;
const isDigital = ['FT8', 'FT4', 'PSK31', 'RTTY', 'MFSK', 'OLIVIA', 'CONTESTIA'].includes(mode);
if (isDigital) {
// dB-report modes (the WSJT-X / weak-signal family, plus the keyboard PSK/RTTY
// group) carry a "-10"-style SNR, not an RST — scale them on the dB axis.
// Sourced from the canonical classifier so this stays in step with the report
// the form pre-fills; a hard-coded list here previously omitted JS8/FST4/JT65/
// JT9/Q65/MSK144/PSK63, whose "-10" default then rendered a wrong 1-bar meter.
if (isDbReportMode(mode)) {
const clamped = Math.min(10, Math.max(-25, numeric));
return Math.round(((clamped + 25) / 35) * 5);
}
Expand Down
11 changes: 10 additions & 1 deletion src/lib/modes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,12 +53,21 @@ const DB_REPORT_MODES = new Set<string>([
'PSK31', 'PSK63', 'RTTY', 'MFSK', 'OLIVIA', 'CONTESTIA',
]);

// Whether `mode`'s signal report is conventionally a dB SNR ("-10") rather than
// an RST. Exposed as the single source of truth so UI that interprets a report
// value — the pre-filled default, a signal-strength meter — classifies a mode
// the same way everywhere instead of each call site carrying its own drifting
// copy of the digital-mode list.
export function isDbReportMode(mode: string): boolean {
return DB_REPORT_MODES.has(mode.trim().toUpperCase());
}

// The signal report a logging form should pre-fill when an operator picks `mode`.
// CW takes RST with a tone digit (599); dB-report digital modes take -10; every
// other mode (phone, digital voice, image, packet) takes a 59 RS(T).
export function defaultRstForMode(mode: string): string {
const m = mode.trim().toUpperCase();
if (m === 'CW') return '599';
if (DB_REPORT_MODES.has(m)) return '-10';
if (isDbReportMode(m)) return '-10';
return '59';
}
43 changes: 42 additions & 1 deletion tests/modes.spec.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { test, expect } from '@playwright/test';
import { AMATEUR_MODES, defaultRstForMode } from '@/lib/modes';
import { AMATEUR_MODES, defaultRstForMode, isDbReportMode } from '@/lib/modes';

// The mode list is the canonical set of operating modes Nextlog can store, and
// the single source of truth the contact-search mode dropdown draws from. These
Expand Down Expand Up @@ -93,6 +93,15 @@ test.describe('defaultRstForMode', () => {
}
});

test('agrees with isDbReportMode for every canonical mode', () => {
// defaultRstForMode and isDbReportMode share one classification: a mode
// defaults to a -10 dB report exactly when it is a dB-report mode.
for (const mode of AMATEUR_MODES) {
if (mode === 'CW') continue; // CW is RST (599), not a dB report
expect(defaultRstForMode(mode) === '-10').toBe(isDbReportMode(mode));
}
});

test('is case-insensitive', () => {
expect(defaultRstForMode('cw')).toBe('599');
expect(defaultRstForMode('ft8')).toBe('-10');
Expand All @@ -105,3 +114,35 @@ test.describe('defaultRstForMode', () => {
}
});
});

test.describe('isDbReportMode', () => {
test('classifies the WSJT-X / weak-signal digital family as dB-report modes', () => {
// These pre-fill a "-10" report in the logging forms, so any consumer that
// renders a signal-strength meter must scale them on the dB axis, not read
// the leading digit as an RST readability. The signal-strength bars on the
// new-contact form previously hard-coded a shorter list that omitted JS8,
// FST4, JT65, JT9, Q65, MSK144 and PSK63, so those modes rendered a
// misleading 1-bar meter.
for (const mode of [
'FT8', 'FT4', 'JS8', 'FST4', 'JT65', 'JT9', 'Q65', 'MSK144',
'PSK31', 'PSK63', 'RTTY', 'MFSK', 'OLIVIA', 'CONTESTIA',
]) {
expect(isDbReportMode(mode)).toBe(true);
}
});

test('classifies phone, CW, digital-voice and image modes as non-dB modes', () => {
for (const mode of [
'SSB', 'CW', 'AM', 'FM', 'DMR', 'DSTAR', 'C4FM', 'YSF', 'M17', 'FREEDV',
'SSTV', 'PACKET', 'ATV',
]) {
expect(isDbReportMode(mode)).toBe(false);
}
});

test('is case-insensitive and tolerates surrounding whitespace', () => {
expect(isDbReportMode('ft8')).toBe(true);
expect(isDbReportMode(' JS8 ')).toBe(true);
expect(isDbReportMode('ssb')).toBe(false);
});
});
Loading