From db5e62c5460512a804dc8788d89f6824677daf69 Mon Sep 17 00:00:00 2001 From: Optio Agent Date: Fri, 24 Jul 2026 07:09:36 +0000 Subject: [PATCH] fix(search): offer every logged band in the band filter (add 2200M/630M/4M/1.25M/33CM/13CM) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Search band filter hard-coded its own lowercase band list that had drifted from the canonical band plan (`AMATEUR_BANDS` in @/lib/bands). The logging forms (new-contact, QuickLogCard) present one-click pills for the full band plan, but the search dropdown omitted 2200M, 630M, 4M, 1.25M, 33CM and 13CM — so an operator could log a QSO on those bands yet never filter it back out. Source the dropdown from the shared band plan instead, eliminating the drift by construction. Server-side matching is already case-insensitive (`UPPER(band) = UPPER($n)`), so the uppercase canonical values match stored data. Co-Authored-By: Claude Opus 4.8 (1M context) --- src/app/search/page.tsx | 9 ++++++++- tests/bands.spec.ts | 20 ++++++++++++++++++++ 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/src/app/search/page.tsx b/src/app/search/page.tsx index ea5a571..3589fc6 100644 --- a/src/app/search/page.tsx +++ b/src/app/search/page.tsx @@ -21,6 +21,7 @@ import LotwSyncIndicator from '@/components/LotwSyncIndicator'; import QRZSyncIndicator from '@/components/QRZSyncIndicator'; import DynamicContactMap from '@/components/DynamicContactMap'; import { useUser } from '@/contexts/UserContext'; +import { AMATEUR_BANDS } from '@/lib/bands'; interface Contact { id: number; @@ -81,7 +82,13 @@ interface PaginationInfo { } const MODES = ['AM', 'FM', 'FT8', 'MFSK', 'RTTY', 'SSB', 'CW', 'FT4', 'PSK31', 'DMR', 'DSTAR', 'YSF']; -const BANDS = ['2m', '6m', '10m', '12m', '15m', '17m', '20m', '30m', '40m', '60m', '80m', '160m', '70cm', '23cm']; +// Band options come from the canonical band plan (@/lib/bands) — the same list +// the logging forms (new-contact, QuickLogCard) store — so every band an +// operator can log is also filterable here. The previous hard-coded lowercase +// list had drifted and silently omitted 2200M, 630M, 4M, 1.25M, 33CM and 13CM, +// making QSOs on those bands unsearchable. Matching is case-insensitive on the +// server (UPPER(band) = UPPER($n)), so the uppercase values match stored data. +const BANDS = AMATEUR_BANDS; interface FilterChip { key: keyof SearchFilters; diff --git a/tests/bands.spec.ts b/tests/bands.spec.ts index 4090854..f5f7000 100644 --- a/tests/bands.spec.ts +++ b/tests/bands.spec.ts @@ -46,4 +46,24 @@ test.describe('bands module', () => { expect(frequencyToBand(100.0)).toBe('OTHER'); expect(AMATEUR_BANDS).not.toContain('OTHER'); }); + + test('covers every band the search filter must offer, including the six the old list dropped', () => { + // The contact-search band dropdown sources its options from AMATEUR_BANDS + // (the same list the logging forms store), so an operator can always filter + // for a band they were able to log. It previously hard-coded a shorter + // lowercase list that omitted these six — a 4M or 630M QSO could be logged + // from a one-click pill but never filtered back out in search. + const previouslyUnfilterable = ['2200M', '630M', '4M', '1.25M', '33CM', '13CM']; + for (const band of previouslyUnfilterable) { + expect(AMATEUR_BANDS).toContain(band); + } + // And the bands the old list did have are still present, so nothing regressed. + const legacySearchBands = [ + '2M', '6M', '10M', '12M', '15M', '17M', '20M', '30M', '40M', '60M', '80M', + '160M', '70CM', '23CM', + ]; + for (const band of legacySearchBands) { + expect(AMATEUR_BANDS).toContain(band); + } + }); });