Skip to content

fix(logging): derive band from frequency consistently across logging forms - #232

Merged
patrickrb merged 1 commit into
mainfrom
optio/task-18b0161a-647d-4d28-8ba8-f7e7f632e0eb
Jul 23, 2026
Merged

fix(logging): derive band from frequency consistently across logging forms#232
patrickrb merged 1 commit into
mainfrom
optio/task-18b0161a-647d-4d28-8ba8-f7e7f632e0eb

Conversation

@patrickrb

Copy link
Copy Markdown
Owner

Problem

The two ham-radio logging forms — the New Contact page (/new-contact) and the dashboard Quick Log card — each carried their own private copy of a freqToBand() helper. Those copies had drifted from the shared, unit-tested frequencyToBand() in src/lib/adif.ts (the one used for ADIF import), so the same frequency could be mapped differently depending on where you were in the app.

The drift caused two user-facing bugs when logging a contact by typing a frequency:

  1. Band auto-fill silently missed several real bands — 60M below 5.33 MHz (the US channelised sub-band, e.g. the 5.107 MHz FT8 spot), 4M (70 MHz), 23CM (1296 MHz), 13CM, 33CM, and the LF/MF digital bands (2200M / 630M). The operator had to set the band manually.
  2. The New Contact page's frequency validation wrongly rejected valid QSOs. Its validateFrequency() used the same narrow copy, so typing e.g. 1296.1 or 5.107 produced "Frequency is outside amateur radio bands" and blocked the log entry.

Example of the drift (60M): the form copies used 5.33–5.408 MHz; the shared/ADIF mapper uses the full ADIF enumeration 5.06–5.45 MHz.

Solution

Consolidate onto a single source of truth.

  • New module src/lib/bands.ts — a pure, server-import-free module holding frequencyToBand() (moved verbatim from adif.ts) plus an ordered AMATEUR_BANDS list. It has no pg/db imports, so it can be imported into client components without dragging the database driver into the browser bundle (which is exactly why adif.ts couldn't be reused directly).
  • src/lib/adif.ts now imports and re-exports frequencyToBand from @/lib/bands, so existing importers and tests that source it from @/lib/adif keep working unchanged.
  • New Contact page and QuickLogCard now import frequencyToBand + AMATEUR_BANDS from @/lib/bands, deleting their local copies. The band pickers render AMATEUR_BANDS so any auto-derived band is a selectable option. The 'OTHER' sentinel returned for out-of-band frequencies is handled at each call site — it means "don't auto-fill" for derivation and "outside amateur radio bands" for validation, preserving the prior behaviour.

Net: −77 / +17 lines across the three touched files; two duplicated mappers become one.

Testing performed

  • npm run typecheck — clean
  • npm run lint — clean
  • npm run build — succeeds
  • npx playwright test tests/bands.spec.ts tests/frequency-to-band.spec.ts10 passed. Added tests/bands.spec.ts covering: every band the mapper emits is present in AMATEUR_BANDS (and in ascending order, no dupes, no 'OTHER'); the @/lib/adif re-export stays in lock-step with the source of truth; and the specific bands the standalone forms previously missed (60M channels, 4M, 23CM, 13CM, LF). The existing frequency-to-band.spec.ts still passes unchanged via the re-export.

Backwards compatibility

No API or DB changes. frequencyToBand's ranges and outputs are identical to before (moved, not modified); only the two client forms change behaviour — and only to gain the correct bands they were missing.

Future follow-up

  • The band-order list in src/components/charts/BandDistributionChart.tsx and the filter-chip band lists (search, filter-chips-demo) are separate hard-coded lists that could also adopt AMATEUR_BANDS in a later consistency sweep.

🤖 Generated with Claude Code

…forms

The New Contact page and QuickLogCard each carried their own copy of a
frequency→band mapper that had drifted from the shared, tested
frequencyToBand() in src/lib/adif.ts. As a result, typing a frequency on
several real amateur bands failed to auto-fill the band, and on the New
Contact page the copies' narrower ranges made frequency *validation*
wrongly reject valid QSOs as "outside amateur radio bands":

- 60M below 5.33 MHz (the US channelised sub-band, e.g. 5.107 MHz FT8)
- 4M (70 MHz), 23CM (1296 MHz), 13CM, 33CM
- the LF/MF digital bands (2200M, 630M)

Extract the band plan into a new server-import-free module src/lib/bands.ts
(frequencyToBand + an ordered AMATEUR_BANDS list) so both the server-side
ADIF importer and the client logging forms share one source of truth —
adif.ts can't be imported into client components because it pulls in the pg
pool. Both forms now import from @/lib/bands; adif.ts re-exports
frequencyToBand for backwards compatibility. Band pickers use AMATEUR_BANDS
so any auto-derived band is a selectable option. The 'OTHER' sentinel is
handled at each call site (no auto-fill / validation failure).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@vercel

vercel Bot commented Jul 23, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
nodelog Ready Ready Preview, Comment Jul 23, 2026 10:11pm

Request Review

@patrickrb

Copy link
Copy Markdown
Owner Author

Review: PR #232 — approve (verdict; could not post as formal review — GitHub blocks self-approval since this PR's author matches the reviewing account)

Reviewed the diff and the task context. This is a clean, well-scoped fix.

What it does: Consolidates three drifted copies of frequency→band mapping (New Contact page, QuickLogCard, and the ADIF importer) into a single source of truth in src/lib/bands.ts. The new module has no server-only imports, so it's safe to pull into client components without bundling the pg driver — a legitimate reason the two forms couldn't just import from adif.ts directly.

Verified:

  • src/lib/adif.ts re-exports frequencyToBand from @/lib/bands with no circular import (bands.ts has no dependency back on adif.ts), so the existing tests/frequency-to-band.spec.ts keeps working unchanged.
  • The 'OTHER' sentinel replaces the old falsy-string check consistently at every call site (validateFrequency, handleChange band auto-fill in new-contact/page.tsx, and both call sites in QuickLogCard) — behavior is preserved, just now correctly covers the 60M sub-band, 4M, 23CM, 13CM, 33CM, and the LF bands.
  • BAND_PILLS/BANDS are now sourced from the exported AMATEUR_BANDS and rendered via .map(); both consuming components store band as plain string (handleSelectBand(value: string), useState<string>), so no type friction from widening the pill list.
  • New tests/bands.spec.ts mirrors the existing test file's style and covers the ordering/dedup invariant of AMATEUR_BANDS, the re-export lock-step check, and the specific previously-missed bands called out in the PR description.
  • No API/DB shape changes; scope is tightly limited to the described bug.

No issues found. Recommend approving.

@patrickrb
patrickrb merged commit 1f15f9f into main Jul 23, 2026
7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant