Phase 5 of the logbook: the exchange, the entry sponsors take, and the
confirmations an award counts.
Contest fields go on the contact — the contest, the serials both ways as
numbers and as words, and the zones — because an exchange is not always a
number: a zone, a section or a name goes in as written. The serial sent and
the contest stay between contacts, since they belong to the session and not
to the contact just logged, and the serial counts on by itself rather than
being retyped forty times an hour.
Cabrillo 3.0 is written because ADIF cannot do this job: sponsors take
Cabrillo and reject everything else. Its shape is not ADIF's either — the
frequency is kilohertz below 30 MHz and a band designator above it, the modes
are CW, PH, FM, RY and DG, and the contacts go oldest first, as a contest log
is read. The header cannot be derived from a log — how many operators, how
much power, what the score is claimed to be — so it comes from the operator,
with single-op, low power, all bands and mixed behind it.
QSL, LoTW and eQSL states are held as ADIF's single letters, and anything
else is refused rather than written: a log that grew states of its own would
be one no other program could read. A contact is confirmed when any one of
the three says so — an award wants a card or an electronic match, not one of
each, and counting them separately would tell the operator they were short of
what they have.
The bands report counts contacts, distinct stations and confirmations per
band, ordered by wavelength as a band plan reads.
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>
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>