Windows parks a minimised window at -32000,-32000 with a stub size and reports
it as not-maximised, so closing OpsLog from the taskbar while minimised stored
exactly that. Seen in a log: "window: saving -32000,-32000 237x39
maximised=false".
The restore side already rejects both the impossible corner and the
below-minimum size, so nothing opened off-screen — but it fell back to the
default placement, and the operator silently lost the size, the position and the
maximised state they had set.
saveWindowState now keeps what was already stored when the window is minimised.
Two tests for that: the Wails flag, and the -32000 corner, because a window that
is mid-close can sit at that corner with the flag already cleared. A poisoned
window.json repairs itself on the next close of an on-screen window.
The stamp answers "PSE QSL", so it reads as its counterpart or not at all: a
bare "TNX" next to a QSL message says thanks for something unnamed.
Changed at the source of the {qso.pse_tnx} token, so every card picks it up
with no template edit. The live indicator in the QSO editor and the hint beside
it follow.
The previous commit added its row to the status table but left the picker
listing only the column-backed channels, so the one channel you could actually
tick was the one you could not select.
Selecting it swaps the left panel for an OpsLog editor instead of the generic
sent/received/date grid, which has no fields to bind to here: Sent is shown
read-only (OpsLog stamps it when the card goes out), Received is the tick, and
the PSE QSL / TNX indicator sits next to them since that stamp is what the flag
is for.
The OpsLog QSL marker sat under QSL Msg in "Contact's details", nowhere near
the channel it belongs to, while the QSL Info tab listed every other
confirmation — QSL, LoTW, eQSL, QRZ.com, Club Log, HRDLog — with a Sent and a
Received column.
It now has its own row in that table and the "QSL received" tick moved to the
same tab, keeping the PSE QSL / TNX indicator that is the reason the flag
exists at all: received prints TNX on the card, otherwise PSE QSL.
Written by hand rather than added to CONFIRMATIONS: that table maps QSO
columns, and this channel is backed by ADIF extras (APP_OPSLOG_QSL_RCVD, plus
the older APP_OPSLOG_QSL_CARD_SENT for the sent side). Sent stays read-only —
OpsLog stamps it when a card actually goes out, and a hand tick would record
something that never happened.
Typing RK3DWA found nothing while RK3DWA/3 found 21 QSOs, so a station's
history was only visible if you happened to type the exact form it had been
logged under — and an operator who worked it as /0, /P or /MM saw none of it.
The other RDA tools and Log4OM fold these together; this does too.
The predicate strips the suffix from what was typed and matches "call = base OR
call LIKE base/%", so it works from either end: the base call finds the portable
QSOs and a portable call finds the plain ones. Deliberately not a bare prefix
LIKE 'RK3DWA%', which would also match RK3DWAB — a different station. The '/' is
what makes it the same operator.
Settings -> General to turn it off. Default ON, hence the inverted storage: an
existing install has no key, and reading that as OFF would leave everyone with
the behaviour we were asked to change.
Contest dupe checking is untouched — it runs through ContestDupe, a separate
binding, and stays an exact match as a contest requires.
Settings -> DX Cluster: a toggle and an interval. When it is on, logging a QSO
announces the station on the master cluster — the QSO's own station callsign as
the DX, on the frequency the contact was made on — so callers find the run
without waiting for someone else to spot it. The node fills the DE field from
the login, so the spotter is us too: a self-spot.
It fires on the FIRST QSO of a frequency and then at most once per interval.
Both halves matter: announcing every QSO would flood the node and get the
station filtered out, while a pure timer would stay silent for minutes after a
band change. A drift of up to 500 Hz still counts as the same run, so nudging
the VFO mid-pileup does not re-announce.
Five minutes is the floor, clamped in SaveSelfSpotSettings as well as in the
input: the limit protects the node from us, so it must not depend on the
frontend. The interval input keeps raw text and clamps on blur — clamping per
keystroke rewrote "10" to "5" as soon as the "1" landed.
Wired into both log paths (manual entry and UDP auto-log) on the async side, so
a cluster that is slow or down never holds up logging. A send failure restores
the previous throttle state, so the next QSO retries instead of sitting out an
interval that produced no spot.
The Kenwood/Elecraft backend deliberately suspends its wire poll while PTT is
held — a K3 answers "?;" to IF; during transmit, and treating that as a fault
used to drop the whole CAT link. The consequence was that nothing watched the
transmitter: a client that crashed, was closed, or simply had its socket shut
under it left the rig on air.
And shutting the socket is routine. reloadCATShare tears the sharing server
down and rebuilds it on every settings save, so a Save while WSJT-X held PTT
was enough. A K3 operator's log shows exactly that: "TX;" at 17:53:09, no "RX;"
ever, the poll silent, and the rig still keyed 29 s later when the CAT link
happened to be rebuilt.
The server now drops PTT when a connection ends and when Stop() is called.
Stop() runs before reloadCAT restarts the backend, so the unkey still reaches
the radio. An atomic Swap keeps it once-only across the two paths.
MD6 is FSK on a Kenwood and DATA on a K3/K4. kenwoodModeToADIF decoded the
digit unconditionally as RTTY, so OpsLog contradicted the mode it had just
set: SetMode writes MD6 for a digital mode on an Elecraft, then ReadState
parsed the IF frame back as RTTY.
A K3 running FT8 therefore showed RTTY in the status bar, logged its QSOs on
RTTY, and — through the shared CAT server — told WSJT-X/JTDX the rig sat in
RTTY while they had just asked for a data mode. Found in a K3 operator's log:
every cat:state line read mode=RTTY on 21.074 FT8, two lines after OpsLog's
own "MD6;".
The digit now resolves to the configured digital mode whenever MD6 means DATA
on this rig — the Elecraft backend, and the "DATA A - MD6" data-mode option
that exists for it. A plain Kenwood still reads MD6 as RTTY.
One station reaches the log SHOUTED by QRZ, lower-cased by a hurried
operator and in whatever case an imported ADIF carried, so the same name
appears three ways across a log. Name and QTH are now title-cased word by
word; Comment and Note only get a capital first letter, because the rest
routinely holds callsigns and modes ("TNX QSO F5ABC, FT8 59") that
lower-casing would destroy.
Normalised on blur, never per keystroke — rewriting the value mid-word
fights the typist (the controlled-input trap in CLAUDE.md). Applied in
both entry layouts and in the QSO editor, since leaving the editor alone
would just reintroduce the mess on the first correction.
The Recent QSOs box only ever searches callsigns, so it upper-cases as
you type and carries an inline clear button.
Award columns were filtered out of the persisted column state entirely,
so applyColumnState({applyOrder:true}) had nowhere to put them and AG
Grid appended what the state does not mention. A column the operator
dragged to sit AFTER an award column therefore jumped back in front of
it on every reload.
Only the `hide` property is dropped now. Visibility stays owned by the
awardShown code-set — that is what stopped shown award columns from
vanishing on an awardCols rebuild — while the position survives the
round-trip.
The Settings -> Database panel had lost every action on the settings
database except "Open folder", which launches Windows Explorer — where a
.db cannot be selected at all, so double-clicking it only asks which
program should open it. The handlers were still there, just no longer
rendered; New / Open existing / Save a copy / Rename / Reset are back,
along with the restart banner (the DB pointer is only read at startup).
The backup block put the rotation field and three checkboxes on one flex
row inside max-w-xl. That fitted until "keep every backup" arrived with
its two-line hint, after which every label wrapped into an unreadable
sliver. The options are stacked now, and keep-all sits under the option
it depends on.
Stale since the 0.23.9 work: SetOpsLogQSLReceived, the backup keep_all
flag, the award PRIMARY search prefix and the default QSL message were
all missing from the generated bindings.
The Elecraft handling was buried in the Kenwood "data mode" option, so users
couldn't find it. Add "Elecraft K3/K4" as its own entry in the CAT backend
selector. It reuses the Kenwood-dialect transport and the Kenwood USB/network
settings (the K3 emulates the Kenwood command set — a separate transport would be
a near-total duplicate), with an `elecraft` flag on the client that forces
digital modes to DATA A (MD6+DT0) — no data-mode dropdown needed. RigState still
reports Backend "kenwood" so the CW-over-CAT keyer capability keeps working.
Integrates the fix/jtdx-kenwood-tx work onto main: the Kenwood/Elecraft backend's
"data" data-mode path now sends MD6 then DT0 so a K3/K4 lands in DATA A — the
audio sub-mode FT8 uses. MD6 alone could leave the rig in an FSK/PSK sub-mode
where it keyed but the rear sound-card audio never modulated ("transmits but
nothing comes out"). The data-mode option is relabelled "DATA A — MD6+DT0
(Elecraft K3/K4)". A shared Kenwood/Elecraft backend is kept (the K3 emulates the
Kenwood dialect); the Elecraft specifics live behind the data-mode seam rather
than a 7000-line duplicate backend.
Only OrRule had a Prefix ("prepended to each found reference", e.g. postal 74 →
D74); the primary search passed "" for prefix, so an award whose references are
D01/D02… couldn't prepend the D on its main rule — only on OR fallbacks. Add
Def.Prefix, pass it into the primary run(), and expose a Prefix input next to the
primary Leading/Trailing fields in the award editor.
Sub-option of "back up on every exit": when on, backup.Run/RunADIF stamp the
filename with the time (opslog-YYYY-MM-DD-HHMMSS.db) so each run is a distinct
file instead of overwriting the day's snapshot. Rotation still trims to the
newest N (raise Rotation for a longer history). Threaded as a `unique` flag
through runConfiguredBackup/backupLogADIF from BackupSettings.KeepAll
(keyBackupKeepAll); UI sub-checkbox shown only when EveryExit is on.
autoLogFromUDP (the inbound WSJT-X/JTDX/MSHV log path) inserted the QSO but,
unlike the manual LogQSO path, never called a.udp.EmitLoggedADIF — so a QSO
received from MSHV was not re-emitted to the outbound ADIF listeners (Log4OM,
N1MM, GridTracker…). Emit it in the same async goroutine. A pathological
self-loop is broken by the existing ±2-min dedup, which returns before the emit.
Received flag: new APP_OPSLOG_QSL_RCVD extra, toggled on the QSO edit window
next to QSL Message (immediate targeted write via SetOpsLogQSLReceived so it sets
AND clears reliably), plus a live PSE QSL / TNX indicator and a Recent-QSOs
column mirroring the sent one.
PSE/TNX card stamp: automatic — received → TNX, otherwise PSE QSL — exposed as
the {qso.pse_tnx} token (added to qslVars, so preview and send agree) and placed
in the default QSO-box footer; it can be moved to its own element in the designer.
Default QSL message: new qsl.default_message (QSLEmailTemplates.DefaultMessage),
edited under Settings → E-mail → QSL card e-mail. qslVars falls back to it when
the QSO's own QSLMSG is empty, so a per-QSO message always wins. Single choke
point covers both live preview and send.
New BackupSettings.EveryExit (keyBackupEveryExit) with a Settings → Backup
checkbox. When on, the three shutdown-backup gates (plannedShutdownSteps,
runBackupForShutdown, maybeShutdownBackup) bypass the HasBackupToday /
HasADIFBackupToday "already done today" check and always run, so a second
session's QSOs are captured instead of skipped until the next day. The dated
backup file for today is refreshed (overwritten) each exit; prior days are still
kept by rotation.
spotStatus updates ~20x/second under an RBN firehose; firing a full refreshCells
on each was pure churn on a slow PC. Coalesce into one refresh per 200ms. Keeps
the NEW/WORKED badge colours and the 'represents nothing' dimming — the dimming
itself is a trivial pure read now that the worked-index scan is cached, so it's
no longer a cost worth removing.
A user on a slow PC with RBN saw OpsLog at 94% CPU and 8.5 GB RAM (Logger32: 3%
/ 34 MB on the same feeds). Two runaway costs under the spot firehose:
- ClusterSpotStatuses re-scanned the ENTIRE logbook (5-6 full-table maps) on
every 50 ms spot batch — ~20×/second — its "one scan regardless of batch" doc
was untrue. On a big log that's millions of row-scans/second → the pegged CPU.
Now cached in clusterStatusCache (an immutable snapshot), rebuilt only when the
logbook changes (noteWorked on a single log, invalidateAwardStats on bulk), so
it's one scan per logged QSO instead of per batch.
- The frontend spotStatus map had no cap: one entry per call|band|mode ever seen,
and RBN produces thousands of unique calls/hour → unbounded growth to GBs, plus
a full {...prev} copy 20×/second. Now pruned back to the live (SPOTS_CAP=1000)
spots once it drifts past 2×, with a cheap same-reference bail-out otherwise.
Since the logbook was split into its own SQLite file, backup.Run was still
snapshotting a.db (the settings/config database) — so the scheduled and manual
backups silently stopped including the QSOs. The operator was backing up config
and thinking it was their log.
Track the resolved logbook file path (a.logDbPath, set in connectLogbook) and
route the backup through a new runConfiguredBackup: the CONTACTS become the
primary "opslog-*" backup (the logbook file on SQLite, an ADIF export on MySQL),
and the settings/config db is snapshotted separately as "opslogcfg-*" so nothing
is lost. backup.Run takes a name prefix; the two sets rotate independently.
The recording e-mail's subject and body round-tripped through EmailSettings and
the backend (keyEmailSubject/keyEmailBody) but had no UI editor — only the QSL
card e-mail did, so the recording mail was stuck on the default text. Add the
same subject/body editor to the E-mail panel, above the QSL block, sharing the
{CALL}/{DATE}/{BAND}/{MODE}/{MYCALL} template variables.
A K3 (and other Kenwood-dialect rigs) answers "?;" to an IF; status poll while
it is transmitting. ask() latched that as "IF unsupported" and ReadState
returned an error, so the Manager dropped the whole CAT link mid-transmit — the
log shows connected=false / "IF rejected" the instant JTDX sent TX;. That tore
down the shared rigctld CAT the digital-mode software keys through.
Two fixes: (1) track PTT (SetPTT) and skip the wire poll while it's held —
ReadState hands back the cached last state, capped at 30 s so a missed
SetPTT(false) can't freeze it; (2) never latch IF/ID as unsupported — those are
universal on Kenwood/Elecraft, so a "?;" to them is a transient busy (mid-TX),
not a missing command.
The Row helper was defined inside AwardsSelectionPanel, so each render minted a
new component type and React remounted the whole list on every click — flicker,
and clicks lost between mousedown and click (hence needing many tries). Inline
the row markup so the keyed buttons keep a stable identity.
New per-profile "tracked awards" selection: Settings → Awards (under User
configuration) is a two-column transfer list — every defined award on the left,
the ones you follow on the right, click to move either way. The Awards tab's
list is narrowed to the followed set; an empty set means "show them all" so the
tab is never blank.
Backend: app_awards_tracked.go adds keyAwardsTracked (per-profile JSON array of
award codes) with GetTrackedAwards/SaveTrackedAwards; saving emits
awards:tracked-changed so the Awards tab re-filters live. Award definitions stay
global — only the follow selection is per profile.
The follow filter correctly leaves the antenna put on an un-ticked band, but the
TX-inhibit loop ran independently: it gagged FlexRadio transmit whenever the
antenna reported "moving" (a stray polled Moving flag, or a move finishing from
the previous band), even on a band OpsLog was deliberately not managing. An
operator working 15 m on another antenna had their FT8 cut mid-transmission by
an "antenna moving" interlock.
motorTXInhibitLoop now takes the covered-band set and forces moving=false when
the current rig band isn't in it — un-ticking a band means hands off entirely:
no tune AND no inhibit. Also correct the RCU-01 references to RCU-06 (the actual
controller behind the short 11-byte status frame).
A contiguous FreqMin/FreqMax range can't drop a single band while keeping its
neighbours — so it couldn't express "40 m and 20 m yes, 30 m no" for an antenna
whose 30 m extension isn't fitted. Replace it with a Covered bands selector
(40 m–6 m) that applies to both the Ultrabeam and the SteppIR.
The follow loop and immediate re-tune now gate on band membership
(motorBandAllowed) instead of a MHz range: only bands in the operator's set are
followed, and 80 m/160 m are never followed (outside a beam's reach) regardless.
The legacy FreqMin/FreqMax is migrated to a band set on load and still
round-trips, so existing configs are unchanged. UI swaps the two number inputs
for a row of toggle chips.
The follow logic skips out-of-range frequencies only when it knows BOTH edges
of the antenna's range. The Ultrabeam path took those edges from the
controller's status frame, but an RCU-01 behind an RS232-to-Ethernet bridge
answers with the short frame that omits FreqMax → the guard saw FreqMax==0 and
disabled itself → OpsLog forwarded every rig frequency, so 80 m (un-tunable)
reached the controller, which clamped the elements down to its lowest band
(~30 m).
ubAdapter now carries the operator-configured FreqMin/FreqMax from Settings →
Antenna (like steppirAdapter already did) and uses them as a hard floor/ceiling,
falling back to the controller-reported bound only when one is left unset. This
guarantees both bounds are present so the guard actually engages. The Tunable
range editor in the Antenna panel, previously SteppIR-only, is now shown for the
Ultrabeam too, with a generic bilingual hint.
New internal/rotator/dcu1 client speaking the DCU-1 command set (AP1nnn / AM1
to go-to, AI1 to read bearing), over a serial COM port (4800 baud default) or a
raw TCP serial-over-IP bridge. Azimuth-only; Stop re-commands the current
bearing since the base set has no stop opcode.
Wired through app.go (normRotorType, default port 4001, GoTo/Heading/Stop/test
switches, park returns the same "PstRotator only" note as ARCO) and exposed in
the rotor settings as "Hy-Gain DCU-1 (RotorCard DXA, Rotor-EZ, Green Heron)"
with the ARCO's serial/TCP transport chooser and a bilingual hint. Protocol is
implemented from the standard DCU-1 spec and still needs field confirmation
against a real RotorCard DXA.
The pop-up listing the QSOs behind a Stats band/mode square gets a cleaner header
(band chip, hint), banded/hover rows and roomier spacing. Callsigns are now
clickable and open the QSO editor (App.openEdit threaded through DetailsPanel →
BandSlotGrid → SlotQSOModal as onEditQso), matching the double-click-to-edit of
the log grids.
Network relay boards (WebSwitch/KMTronic/Dingtian) were always reached at
http://<host>, so a board on the LAN behind a reverse proxy — the common remote
setup where the operator's 80/443 already go to Nginx Proxy Manager — couldn't be
reached from outside. relayBase() now keeps a scheme the operator supplies
(https://relay.example.com, optional sub-path) and only defaults to http:// for a
bare host/host:port. UI hint + test added.
setSpotStatus({}) blanked EVERY spot's status, so logging one contact wiped all
the other NEW/NEW-BAND badges and they popped back a moment later. Instead,
re-fetch the shown spots and OVERWRITE each status in place (merge) — the just-
worked spot updates while the rest stay put, no flicker. Same visibility gate
and 2 s debounce; backend cost is one status batch (one logbook scan) as before.
A just-worked call/entity/POTA/prefix/slot kept its NEW badge because per-spot
status is cached and never expires. Drop the cache on qso:logged so the grid
re-evaluates. Kept cheap for old machines / big logbooks: only fires while the
cluster is on screen (a log while hidden marks it dirty and refreshes once on
next open), and debounced 2 s so a fast run coalesces instead of re-scanning the
logbook per QSO.
The "dim spots that represent nothing" feature drove the dimming with getRowStyle
and called api.redrawRows() on EVERY spotStatus update to re-apply it — a full
grid re-render on a firehose of status updates, which pegged the CPU on a busy
cluster (reported as PCs slowing down since 0.23.6). Move the dimming to a
defaultColDef cellClassRules ('opacity-40') that the existing light
refreshCells({force:true}) re-applies; drop redrawRows and getRowStyle.
Confirmed live: "setup fanmode=CONTEST" is accepted (reply code 0) while the
bare "fanmode=CONTEST" we switched to earlier is rejected (0x50000015), so the
fan mode snapped back and never changed on the amp. Restored the "setup " verb
and now check the reply code, returning an error if the amp rejects the set.