Award refs are now computed once and stored on the QSO as a JSON column
(award_refs, e.g. {"DDFM":"74","WAJA":"12"}) instead of recomputing the whole
award engine on every grid page load. Written on log/edit/UDP-import and
bulk-recomputed when an award definition or reference list changes; a one-time
per-logbook backfill materializes pre-existing QSOs. The grid reads the column
directly like any other field, so it's fast and available everywhere (including
the shared MySQL logbook).
Also fix per-profile grid column layout: the DB copy was already per-profile,
but the localStorage cache used one global namespace and shadowed it, so a
profile switch kept the previous profile's columns/widths. gridPrefs now scopes
the cache by active profile id and the grids remount on profile:changed.
GetQSORate now returns both the active operator's rate (their own performance)
and the whole station's rate (all operators combined). RecentRateBreakdown
computes both in one scan of the recent rows (per-operator + all-operators),
replacing RecentRate.
The multi-op live status heartbeated every 15 s, so an operator who left the
log open but stopped working still showed on air. Now it publishes an explicit
online flag: online when a new contact was logged within the last 5 minutes,
offline otherwise. live_status gains `online` + `last_qso_at` columns (added to
existing tables via ALTER). Logging a QSO (manual, UDP, ADIF monitor) stamps the
time and republishes at once, so they flip back online instantly.
At launch the last-QSO time is seeded from the DB (Repo.LastQSOTime), so an
operator who worked someone just before starting OpsLog is shown online right
away rather than offline until their next contact. LiveLastQSOAgeSec exposes it
to the UI badge.
Status clobber: sending an OpsLog QSL card (or stamping a recording) read the
QSO, did slow work (e-mail send), then wrote the WHOLE row back from that now-
stale copy — silently reverting clublog/qrz_qso_upload_status from Y to R that a
concurrent auto-upload had just set. That's why QSOs "showed R again, but only
with QSL sending on", intermittently. New Repo.SetExtra does a targeted
`UPDATE qso SET extras = ?` that touches only the extras column, so an app-field
stamp can never clobber another column. Used by SendEQSL, markRecordingSent and
the recording-path stamp.
Logging speed: the log path no longer runs a callsign lookup at all — the e-mail
already arrives on the QSO from the entry lookup, so the second one was
redundant and stalled logging on a slow/not-found QRZ. And the entry lookup
(LookupCallsign) is now bounded to ~2 s: providers get 2 s, then it falls
through to cty.dat instead of spinning 10 s+ on a call that isn't in QRZ.
Automatically switches the Station Control relay boards from the rig's
current frequency / band. Each relay carries one rule: off (manual), a
frequency window (ON inside [lo,hi] kHz, OFF outside), or a set of bands
(ON on those bands, OFF elsewhere). Evaluated on every CAT frequency/band
change; a relay is only switched when its desired state actually changed,
so tuning within a range doesn't hammer the board.
A cached atomic flag keeps the CAT hot path a no-op when the feature is off
(important during FT8 slice churn). Saving re-applies from the live
frequency so a changed rule takes effect immediately.
New Settings → Hardware → Relay auto-control section: master enable plus a
per-relay mode (Off / Frequency / Band) with kHz range inputs or band
chips, per configured relay board. i18n EN + FR. Azimuth/Time modes (the
other two PstRotator tabs) are left for later.
Watches a configurable list of external ADIF files (fldigi RTTY logbook,
N1MM, VarAC…) and imports newly appended QSOs automatically. Each record
goes through the existing UDP-log path, so it gets full enrichment, ±2-minute
dedup (shared udpLogMu, can't race the UDP auto-log) and automatic upload to
the configured external services — no per-file toggles like Log4OM.
A newly added file starts at its current size (offset -1 sentinel → size on
first scan), so the QSOs already in it are NOT bulk-imported; only contacts
logged after it was added come in. Reads only up to the last complete <eor>
so a half-written record waits. Handles truncation/rotation (re-reads from 0,
dedup protects) and persists per-file offsets without clobbering a concurrent
UI edit of the file list.
New Settings → ADIF monitor section: master enable + add/remove/toggle files.
Backend emits adifmon:imported → the grid refreshes and a toast reports the
count. i18n EN + FR.
Club Log: on a failed batch, log the callsign#id of every QSO in it so a
per-record rejection (e.g. a field value nginx's WAF blocks with a 403) can
actually be located instead of hiding behind "batch FAILED".
FCC ULS: catch the maintenance bounce before following it. data.fcc.gov
redirects to www.fcc.gov/system-maintenance during maintenance windows, and
that page then HTTP/2-stream-errors — which surfaced as a cryptic
INTERNAL_ERROR. Detect the redirect and return "try again later".
Opt-in via Settings→General (portable pref opslog.showQsoRate). Shows the
contest-style QSO rate in QSOs/hour, projected from the trailing 10-minute
(count ×6) and 60-minute windows, between the widget icons and propagation.
Backend: qso.RecentRate counts QSOs whose start time falls in each trailing
window, scanning only the last 400 rows (cheap on a large log); App.GetQSORate
exposes the 10/60-min counts. Frontend refreshes on qso:logged and a 30s tick.
The meter shares the propagation grid cell — the header is a fixed 6-column
grid, so adding it as its own child pushed profile/band-map/compact onto a
second row. i18n EN + FR.
Backend: replace the bounded cluster-event channel (which dropped spots
under RBN bursts once enrichment/UI fell behind) with an unbounded
clusterQueue (slice + sync.Cond). The socket-read goroutine still never
blocks, but a burst now grows the backlog and drains instead of losing
spots. Head compaction keeps a sustained burst from growing the backing
array without bound.
ClusterGrid: drop all hard-coded light-theme hex colours (Time, Country,
Spotter, Comment, POTA, band/mode fills) for semantic CSS-var tokens so
they read correctly on every theme. Replace the heavy full-cell band/mode
fills with small rounded pills (cellChip). Only NEW MODE pills the mode
cell — NEW SLOT (band and mode each worked, just not together) no longer
highlights the mode cell, which wrongly implied the mode was new.
App: stage incoming spots ~50ms and resolve their slot status before
inserting, so a row paints with its badge already on instead of flashing
plain text then flipping to a pill. Self-spot toast still fires per spot.
i18n: translate the General settings that were still English — telemetry
and live-status toggles, the Main view pane pickers and their options.
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
New Operator picker next to the contest picker, populated with the operators
actually present in the log. Selecting one recomputes every statistic for that
operator — QSO/DXCC totals, top countries, continent split, by band/mode, and
the rate/best-60. "— Station owner —" buckets QSOs logged with no OPERATOR set.
Shown only when the log has more than one operator.
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
The right-click "Update from QRZ" only mapped QTH (city), name, state, county,
grid and zones — the street address and a few other fields QRZ returns were
dropped, so it looked half-updated next to the edit window's Fetch button.
Aligned it to fill the same set: address, email, qsl_via and lat/lon too.
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>