[feat](trx-rs): keep a station log, and a layout to work the bands from #59

Closed
sjg wants to merge 0 commits from feat/logbook-and-ham-layout into main
Owner

Closes #54. The logbook and the ham layout, in the shape the proposal settled on (merged as part of docs/logbook-ham-layout-proposal).

The crate

New trx-logbook under src/trx-client/: the contact, the ADIF reader and writer, the file, and the rules for telling one contact from two. No new dependencies.

ADIF, because it is the only thing the ecosystem reads — LoTW, eQSL, Club Log, QRZ and every other logger take it and nothing else, so a log that cannot write .adi cannot be uploaded, confirmed, or moved. The reader is forgiving in the ways real files are irregular: lowercase tags, CRLF, missing header, unknown fields, and a declared length that is the only thing that can end a value (ADIF values may contain <). Fields it does not model are carried through import to export unchanged, so a round trip does not strip what another program wrote. A record that cannot be read is reported and the rest of the file still loads.

The file is JSON Lines, appended one line per contact. A log is the one thing here that cannot be regenerated, and the bookmark store's whole-file dump would rewrite megabytes to log one contact — and lose all of them if the power went halfway. An append costs at most the record being written, which a test proves by tearing a line in half and reopening. Edits append revisions, deletes append tombstones, and the file compacts through a temporary and a rename when the superseded outnumber the live.

Dedupe is callsign + band + mode within two minutes, compared as instants so midnight matches, with modes normalised so an imported SSB finds our USB.

The panel

Its own tab, standing in every layout. An entry opens with six fields and no more — frequency, mode and rig name from the radio, the time from the server, and the callsign and locator of whatever decode it was started from. A report stays empty: an FT8 SNR is not what was sent. Your own callsign and locator are station identity on the top line, not per-contact fields.

Times are the server's, and the panel says so when the browser's clock is more than a second out. Worked-before answers as a callsign is typed. Import reports added, already-held and unreadable counts.

A decode is not a contact: the Log button on an FT8 or APRS row opens an entry and logs nothing by itself.

The layout

ham joins compact/broadcast/digital/full, opening on the logbook with the radio controls around it, gated on RigCapabilities.tx — a receiver has no contacts to log.

Two bugs found on the way

  • A frequency of a whole number of MHz ending in a zero rendered as a tenth of itself — 20.000000 trimmed of trailing zeros is 2, because the run reaches back through the decimal point. Present in both the Rust and the TypeScript formatter I had just written; both fixed, with the regression pinned at 20, 100 and 10 MHz.
  • The API sits under /api/logbook, not /logbook. /bookmarks is registered as both an API and a page and whichever comes first wins; the logbook does not inherit that.

Verification

  • trx-logbook: 27 unit tests — ADIF round trip, a file from another logger, rejected records, truncation, the declared-length rule, store reopen/edit/delete/tear/compaction, dedupe across the window and midnight, worked-before.
  • trx-frontend-http: 3 new endpoint tests — write → list → worked → export, a read-only session refused, a contact with no callsign refused.
  • New tests/logbook.mjs in the browser suite: the log renders newest-first with sidebands, the entry pre-fills exactly six fields, worked-before flips as the callsign changes, logging writes and clears, a Log button on an FT8 row lands on the panel with the callsign, locator and FT8 (not the rig's DIG) and logs nothing, and the ham layout is offered with TX and withheld without it.
  • Whole workspace: 54 test targets pass, cargo fmt --check and clippy --all-targets clean, trx-rs.toml.example regenerated.
  • Docs: the manual gains a Logbook section, CLAUDE.md the crate, and the plan is marked as phases 1–4 done.

Phase 5 (contest exchange fields, Cabrillo export, QSL/LoTW fields) is not included, as proposed.

Closes #54. The logbook and the ham layout, in the shape the proposal settled on (merged as part of `docs/logbook-ham-layout-proposal`). ## The crate New `trx-logbook` under `src/trx-client/`: the contact, the ADIF reader and writer, the file, and the rules for telling one contact from two. No new dependencies. **ADIF**, because it is the only thing the ecosystem reads — LoTW, eQSL, Club Log, QRZ and every other logger take it and nothing else, so a log that cannot write `.adi` cannot be uploaded, confirmed, or moved. The reader is forgiving in the ways real files are irregular: lowercase tags, CRLF, missing header, unknown fields, and a declared length that is the only thing that can end a value (ADIF values may contain `<`). Fields it does not model are carried through import to export unchanged, so a round trip does not strip what another program wrote. A record that cannot be read is reported and the rest of the file still loads. **The file is JSON Lines**, appended one line per contact. A log is the one thing here that cannot be regenerated, and the bookmark store's whole-file dump would rewrite megabytes to log one contact — and lose all of them if the power went halfway. An append costs at most the record being written, which a test proves by tearing a line in half and reopening. Edits append revisions, deletes append tombstones, and the file compacts through a temporary and a rename when the superseded outnumber the live. **Dedupe** is callsign + band + mode within two minutes, compared as instants so midnight matches, with modes normalised so an imported `SSB` finds our `USB`. ## The panel Its own tab, standing in every layout. An entry opens with **six fields and no more** — frequency, mode and rig name from the radio, the time from the server, and the callsign and locator of whatever decode it was started from. A report stays empty: an FT8 SNR is not what was sent. Your own callsign and locator are station identity on the top line, not per-contact fields. Times are the server's, and the panel says so when the browser's clock is more than a second out. Worked-before answers as a callsign is typed. Import reports added, already-held and unreadable counts. **A decode is not a contact**: the Log button on an FT8 or APRS row opens an entry and logs nothing by itself. ## The layout `ham` joins `compact`/`broadcast`/`digital`/`full`, opening on the logbook with the radio controls around it, gated on `RigCapabilities.tx` — a receiver has no contacts to log. ## Two bugs found on the way - A frequency of a whole number of MHz **ending in a zero** rendered as a tenth of itself — `20.000000` trimmed of trailing zeros is `2`, because the run reaches back through the decimal point. Present in both the Rust and the TypeScript formatter I had just written; both fixed, with the regression pinned at 20, 100 and 10 MHz. - The API sits under `/api/logbook`, not `/logbook`. `/bookmarks` is registered as both an API and a page and whichever comes first wins; the logbook does not inherit that. ## Verification - `trx-logbook`: 27 unit tests — ADIF round trip, a file from another logger, rejected records, truncation, the declared-length rule, store reopen/edit/delete/tear/compaction, dedupe across the window and midnight, worked-before. - `trx-frontend-http`: 3 new endpoint tests — write → list → worked → export, a read-only session refused, a contact with no callsign refused. - New `tests/logbook.mjs` in the browser suite: the log renders newest-first with sidebands, the entry pre-fills exactly six fields, worked-before flips as the callsign changes, logging writes and clears, a Log button on an FT8 row lands on the panel with the callsign, locator and `FT8` (not the rig's `DIG`) and logs nothing, and the ham layout is offered with TX and withheld without it. - Whole workspace: 54 test targets pass, `cargo fmt --check` and `clippy --all-targets` clean, `trx-rs.toml.example` regenerated. - Docs: the manual gains a Logbook section, `CLAUDE.md` the crate, and the plan is marked as phases 1–4 done. Phase 5 (contest exchange fields, Cabrillo export, QSL/LoTW fields) is not included, as proposed.
sjg added 1 commit 2026-08-07 21:12:32 +02:00
[feat](trx-rs): keep a station log, and a layout to work the bands from
CI / lint (pull_request) Successful in 2m23s
CI / test (pull_request) Successful in 9m12s
CI / frontend (pull_request) Successful in 5m52s
CI / reuse (pull_request) Successful in 6s
18b2d0efe6
The logbook of issue #54, in the shape the proposal settled on.

A new crate, trx-logbook, holds the contact, the ADIF reader and writer, the
file, and the rules for telling one contact from two.  ADIF because it is the
only thing the ecosystem reads: LoTW, eQSL, Club Log, QRZ and every other
logger take it and nothing else, so a log that cannot write .adi cannot be
uploaded, confirmed or moved.  The reader is forgiving in the ways real files
are irregular -- lowercase tags, CRLF, missing header, unknown fields, a
declared length that is the only thing ending a value -- and carries what it
does not model through to the export, so a round trip does not strip what
another program wrote.

The file is JSON Lines, appended one line per contact.  A log is the one
thing here that cannot be regenerated, and the bookmark store's whole-file
dump would rewrite megabytes to log one contact and lose all of them if the
power went halfway; an append costs the record being written and no more,
which a test tears a line in half to prove.  Edits append revisions, deletes
append tombstones, and the file compacts when the superseded outnumber the
live.

The panel is its own tab and stands in every layout.  An entry opens with six
fields and no more -- frequency, mode, rig name, time, and the callsign and
locator of whatever decode it was started from.  A report stays empty: an FT8
SNR is not what was sent.  Times come from the server, because the browser
may be a phone in another timezone, and the panel says so when the two
disagree by more than a second.  Worked-before answers as a callsign is
typed.

A decode is not a contact, so the Log button on an FT8 or APRS row opens an
entry and logs nothing by itself.

The ham layout is the fifth operator layout, opening on the logbook with the
radio controls around it, offered only where the rig can transmit.

Two bugs found on the way, both in code written here: a frequency of a whole
number of megahertz ending in a zero rendered as a tenth of itself, in Rust
and in TypeScript alike, because trimming trailing zeros from "20.000000"
walks back through the point.  The API also sits under /api/logbook rather
than /logbook, so it cannot shadow its own page the way /bookmarks does.

Closes #54

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SyX26FCpMQxiBoC7r5K1A7
Signed-off-by: Stan Grams <sjg@haxx.space>
sjg closed this pull request 2026-08-07 21:50:14 +02:00

Pull request closed

Please reopen this pull request to perform a merge.
Sign in to join this conversation.
No Reviewers
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: sjg/trx-rs#59