Commit Graph
337 Commits
Author SHA1 Message Date
rouggy 0c0e8b06ba feat(dvk): twelve slots; perf: Settings pauses the heavy streams
The voice keyer grows to F1-F12 — the UI was already data-driven, so the
count constant, the F-key range and the labels are the whole change.

And the preferences stop stuttering on a busy station: every spot batch,
decode batch and CAT snapshot re-rendered the entire App tree behind the
dialog — cluster grid, decodes panel, thousands of nodes — and the pointer
janked over the very panel the operator was trying to use. While Settings is
open the flushes park their batches in the pending refs (bounded) and CAT
repaints at most every two seconds; closing the dialog drains everything.
2026-08-30 20:09:44 +02:00
rouggy 9bd6d988aa feat(udp): spot clicks switch the decoder's mode
Configure (message 15), mode field only, every other field sent as
no-change: an FT4 spot clicked while WSJT-X sits in FT8 lands the operator
ready to decode instead of staring at gibberish. Sent to every instance
heard this session — one already in the right mode treats it as a no-op —
and only for modes the decoder actually speaks (FT8/FT4/JT65/JT9/MSK144/
Q65/FST4); CW and SSB are none of its business. Toggle in the Connections
panel, on by default.
2026-08-30 15:33:07 +02:00
rouggy 997bc81d5e fix(udp): edge-detect the decoder's DX Call stream
WSJT-X restates the same DX Call in every Status packet, and a spot clicked
in OpsLog put that call into the last-UDP memory (deliberately, for another
case) — so the guard let the decoder's next restatement straight through and
the entry snapped back to old news. The decoder's stream now only counts when
its value CHANGES; a clear over there resets the edge so re-selecting the
same station still lands. Explicit picks (N1MM, remote, spot relays) are
untouched.
2026-08-30 15:29:07 +02:00
rouggy 3c59507bc3 feat(udp): Highlight Callsign and Replay — the decoder becomes log-aware
Message 13 paints callsigns in WSJT-X/JTDX's own Band Activity window with
verdicts from the same cluster status cache that colours the spot grid:
watchlist pink, new DXCC green, new band for its entity orange. Deduplicated
per instance+call+verdict, one datagram each; a verdict that lapses (the
operator worked them) clears that call, and turning the option off clears
everything via the protocol's CLEARALL!. Off by default, switch in the
Connections panel.

Message 7 asks a program heard for the first time this session to replay the
decodes already on its screen, so the FT decodes panel starts full instead of
empty until the next period. Replayed lines arrive marked not-new and are
shown but never auto-answered — the auto-caller now checks, on top of its
30-second freshness gate.
2026-08-30 15:25:58 +02:00
rouggy 7b15b534cd fix(wb): latest lookup wins the worked-before panel
Two lookups race when a second callsign is typed before the first answered,
and the slow one landed last: the grid showed the previous call's QSOs under
the new call's name — LU5AVM's contact filed under LW8ETV, screenshot in
hand. A request token now discards any response that is not the newest.
2026-08-30 11:26:47 +02:00
rouggy dad762a0f1 fix(dvk): repair the auto-CQ trace mangled by the previous commit
Shell quoting ate the template literals; the traced loop now compiles.
2026-08-30 02:52:04 +02:00
rouggy 97567d15dd debug(dvk): trace the auto-CQ loop into the app log
It keeps 'not repeating' on a network rig and every guess at which guard was
tripping proved wrong. Each round now logs play / playing-flag / end / gap,
and every exit names its reason.
2026-08-30 02:51:05 +02:00
rouggy ce9ea10d68 fix(dvk): a transiently blank mode no longer kills the auto-CQ loop
Over the network the rig's mode read fails now and then and the CAT push
blanks the mode for a poll or two; the auto-CQ loop treated that blank as
'not a phone mode' and stopped after the first call. Only a KNOWN non-phone
mode stops the loop now.
2026-08-30 02:42:20 +02:00
rouggy 6bf759b6ae fix(cw): Icom CI-V CW works over the network backend too
The CI-V keyer is the same 0x17 command on the same IcomController whatever
carries the link; only the warnings and enable checks insisted on the USB
backend, telling a network operator their working keyer would fail.
2026-08-29 16:39:43 +02:00
rouggy 43e5088e96 fix(icom): the live radio names MY_RIG, and a manual take records it too
Two halves of the same station confusion. The entry form pre-filled MY_RIG
from the per-band default on every band change, so the log-time priority (the
radio that is keying beats the radio that was planned) never ran — the field
was no longer empty by the time the backend looked. The form now asks the
active radio first, through a new ActiveRadioMyRig binding.

And the manual record button started its capture from the From-radio sound
card unconditionally, where the automatic recorder already knew a network
Icom's audio is pushed from the 50003 stream: a manual take on the IC-7760
was a faithful recording of the Flex's DAX. Same source rule for both, and
the button is offered with no sound card at all when the network stream is
the source.
2026-08-29 16:35:25 +02:00
rouggy 582fa561b2 fix(rec): a fresh take starts its clock at zero, not at the last take's total
Two effects, wrong order: the ticking effect ran before the reset effect on a
new take, so its closure captured the PREVIOUS take's elapsed as its starting
point — the counter showed 0 for one second and then jumped back to thirty
minutes. Effects run in declaration order; the reset now comes first.
2026-08-29 16:31:38 +02:00
rouggy ea6fb1c29c fix(flex): send the spot-click zoom after the tune, not alongside it
Double-clicking a cluster spot tuned the slice and zoomed the panadapter in
the same breath. On a cross-band jump SmartSDR moves the panadapter itself to
follow the slice, and that follow lands after — and overrides — a zoom sent in
the same instant, so the width the operator configured never appeared. A spot
clicked ON the panadapter never hit this, because the radio had already tuned
before our zoom arrived; the two paths now behave the same. The Chase new
panel's pick is sequenced identically.
2026-08-29 02:45:07 +02:00
rouggy 29267a865c fix(flex): the Chase new panel's spot click zooms the panadapter too
Every other spot click — cluster grid, band map, watchlist, alert list — goes
through handleSpotClick, which applies the operator's per-mode panadapter
width after the tune. The Chase new panel had its own pick handler that tuned
and stopped there; it now makes the same view change, after the tune for the
same reason.
2026-08-29 02:37:13 +02:00
rouggy f0d3bed8a2 style(stats): room for a long entity beside its badges
The F1 panel widens 560 → 600 px, the header row drops its outer padding and
wide gaps, and the country badge tightens — so 'Central African Republic' and
the NEW SLOT chip share one line instead of the chip dropping alone underneath.
2026-08-29 01:11:02 +02:00
rouggy 7322e2ab66 feat(watchlist): dockable in the Main tab; sticky filters; Needed-only hides worked lines
The watchlist joins the Main-tab pane list (Settings → Main view), rendered in
a card like the other docked panels.

The three filters persist across tab switches — the component unmounts on every
switch, and filters that reset each time are filters nobody trusts.

Needed-only now hides the worked SPOT LINES as well as the all-worked cards,
which is DXHunter's own Not-Worked behaviour and the reported complaint: a
filter that says needed and still lists five green Worked rows answers a
different question than the one asked.

The add-row checkbox is relabelled 'as contest' — it marks the entry being
ADDED, and read as a filter beside the All/DX/Contest buttons it looked
redundant.
2026-08-29 00:39:07 +02:00
rouggy e4abcda94f feat(watchlist): the DXHunter watchlist as an OpsLog tab
The concept, transplanted: a list of callsigns or prefixes being hunted,
matched against the live spot stream, one card per entry with the spots
underneath and the two questions that matter answered on every line — is this
slot still needed, and what is it worth (the cluster's own NEW badges, read
from the same status index).

The file is DXHunter's own watchlist.json, field for field, ClubLog block
included though phase 2 will fill it — a file that round-trips unchanged is the
whole of 'same format', and a test pins it with a real DXHunter entry. Global
(dataDir), not per profile.

CONTEST is per entry, not the global mode DXHunter has: a contest entry is
judged against the current UTC day — the boundary lives in the query's date
bound, so midnight needs no timer and resets nothing. Normal entries read the
same in-memory worked index the alerts use. Prefix matching is why RI0SP
catches RI0SP/MM, pinned by test.

Notify goes through the existing alert:fired event — the frontend already
toasts and sounds it — throttled to one alert per entry per two minutes,
because a DXpedition lights every skimmer on the planet.

Tab wired like NET Control: opt-in from Tools, persisted, closable. Single
click fills the callsign, double click works the spot — the cluster's own
gesture, kept.
2026-08-29 00:25:40 +02:00
rouggy bd8719ce99 feat(cluster): chase switches for POTA and SOTA
An operator who does not chase parks does not want NEW POTA shouting from every
activator spot. Two switches (Settings → DX Cluster, on by default): off, the
marker is withdrawn at the display layer — applySpotDisplay, the same chokepoint
the grid, the band map and the filter read through — so the badge, the colour,
the filter chip and the reference column all go quiet together, and a new-band +
new-POTA spot reads NEW BAND alone. The facts keep being computed; only the
telling stops, so ticking the box back on needs no rescan.
2026-08-28 23:48:22 +02:00
rouggy 0f4e31853b fix(cluster): slot colour apart from POTA, chips match badges, NEW CALL filter works
Three from review. Aqua for NEW SLOT sat right next to the POTA green — it is
now the sky cyan the panadapter palette has shipped for new-slot all along.
The NEW COUNTY chip was green while its badge is violet, and NEW SLOT's chip
was still caution yellow: chips now carry the badge's own colour (color-mix for
the muted border/背景), because a filter that does not look like what it selects
has to be learned twice.

And the NEW CALL chip matched only the DISPLAYED status, which is manufactured
by the slot-highlight option — with that option off the chip matched nothing
and read as broken. It now filters on the fact itself (worked_slot false), so
it works either way.
2026-08-28 23:29:08 +02:00
rouggy 2fb4a4d0ce fix(spot): in split, pre-fill the RX frequency — where the station is
The Send-spot dialog took catState.freq_hz, which is the TX frequency. Working
a DXpedition split, that is where WE transmit: the spot went out five up from
the DX, pointing the whole cluster at the pile-up instead of the station. The
RX frequency is where we listen — where the DX is — and is what a spot names.
2026-08-28 23:21:17 +02:00
rouggy 73cae855ed chore(labels): park the label designer and printing on feature/labels
The feature needs more rounds than the next release can wait for, so main goes
back to before it: the packages, bindings, migration, UI and i18n all move to
the feature/labels branch, which holds every commit. The 0.26.23 block keeps
only the Station column.

The 0031 migration may already have run on a machine that launched a dev build;
the two label tables it created are inert and the recorded migration row is
harmless — the runner only applies filenames it has, so re-adding the migration
when the branch merges will skip cleanly there and apply everywhere else.
2026-08-28 21:35:50 +02:00
rouggy 5ee0ade54b feat(labels): print the labels — queue, address review, PDF, log update
The printing session in three steps. The worklist is the paper queue (ADIF
qsl_sent R/Q), grouped by callsign since several QSOs of one station share a
card. Each recipient is reviewed before anything prints: routing first — via
manager when qsl_via says so, direct when an address is known, bureau otherwise
— then the address itself, editable and fetchable from QRZ (the MANAGER's
address when routing says via). What is printed is the reviewed text verbatim,
not a re-resolution that could differ from what was checked.

One PDF per label kind, never one file for all: a roll printer holds one stock
at a time, and a file mixing 29 mm addresses with 62 mm QSO labels could not be
printed at all. Pages are rasterised by the designer's own renderer at the
stock's dpi and carried into the PDF untouched — internal/pdf is a hand-written
image-page writer (DeviceGray + flate: monochrome, lossless, no cgo) because
that is the entire need.

Bureau stations get no address label (no envelope); return labels are printed
one per envelope. Finishing offers the log update: QSL_SENT=Y, the chosen date,
via B or D per routing — through the same BulkUpdateQSL the paper view uses.
2026-08-28 19:58:37 +02:00
rouggy 1a169fdb4f feat(labels): the Label Designer — QSO and address labels for paper QSL
The designer's data model lives in internal/labels: STOCKS are the physical
roll in the printer (geometry in mm, margins, dpi — seeded with the common
Brother DK sizes, the QL family being what prompted the feature), TEMPLATES are
one design each, of two kinds: the QSO label glued on the card, whose repeating
table carries several contacts of the same station, and the address label for
the envelope, whose lines collapse when a variable is empty.

Everything is measured in millimetres — labels are sold in mm, and an operator
lining a design up against a physical sticker thinks in mm; pixels exist only in
the renderer, at the stock's dpi. The canvas renderer is shared between the
editor's preview and the future print path, so there is no second
implementation for the preview to disagree with.

The preview is fed with the log's latest contacts rather than lorem ipsum: real
data shows a too-narrow column immediately.

Printing (PDF, one page per label at exact size) is the next module; nothing
here prints yet.
2026-08-28 19:41:15 +02:00
rouggy 8c7e1c1a3d fix(cluster): group duplicates by call AND band AND mode
The grouping keyed on the callsign alone, so every spot of a station outside the
first one's band and mode was dropped from the list. RI1FJL spotted on 17m CW,
20m, 30m, 12m and 15m FT8 showed as a single row: four slots gone from the one
view an operator uses to find them.

A duplicate is the dozen skimmers that all heard the same CQ — same station,
same band, same mode. The same station on another band is the opposite of a
duplicate; it is what the chaser is scanning for.
2026-08-28 16:25:06 +02:00
rouggy 3e794f57e0 perf(cluster): widen the spot batching window with the rate of the feed
Every flush commits state the whole window re-renders on, and the window was a
flat 50 ms — twenty full renders a second under an RBN firehose. That is paid
everywhere else in the interface, and it is what a dropdown highlighting its
entries a beat behind the mouse actually is.

The window now follows the rate: 50 ms on a quiet cluster, where a spot should
appear the moment it arrives; 200 ms past five a second; 500 ms past twenty,
where half a second's delay on a line already scrolling past is invisible.
2026-08-28 15:06:08 +02:00
rouggy 30f74583ff style(cluster): one header row in the docked pane
The pane drew its own title bar above the grid's toolbar, so the cluster spent
two lines on headers where Recent QSOs beside it spent one — in a pane often
only a few spots tall. The grid's toolbar now takes what the caller wants on its
left, and the pane passes its title, live count and Filters button there.
Reported by VK4DX.
2026-08-28 13:15:41 +02:00
rouggy 766b0f95a4 feat(tci): key CW through the radio's own macro keyer
The sixth CW engine, and the one the TCI backend was left without. A SunSDR
keys from the WebSocket that already carries the CAT: no WinKeyer, no second
serial port, nothing to wire.

    CW_MACROS:0,<text>;     send
    CW_MACROS_SPEED:<wpm>;  speed
    CW_MACROS_STOP;         abort

Confirmed against the TCI command table in ars-ka0s/eesdr-tci — the source that
settled SPOT. CW_MACROS rather than TCI 2.0's CW_MSG: it exists from 1.6, and
OpsLog resolves its own variables, so the before/after callsign fields have
nothing to carry.

Commas and semicolons are stripped before sending. They are the protocol's own
separators: a comma would become another argument, a semicolon would end the
command with the message half keyed and the rest parsed as a command.

Only the MACRO speed is set, not the paddle keyer's — an operator who set their
paddle to 28 wpm did not ask for it to change because a macro went out at 25.
And there is no backspace: TCI can stop a message but cannot un-type one, so the
type-ahead correction the Flex engine offers is absent rather than faked.
2026-08-28 13:01:55 +02:00
rouggy 9a21c936b1 feat(units): show distances in miles
Everything is computed in kilometres and converted once at display time, in
lib/units — storing miles anywhere would give the same number two sources of
truth and a rounding error that grows with every hop.

The grids capture the unit inside their column definitions, header and formatter
both, so a change is published to them the way the date format already is;
without that a toggle would only appear after a language change or a restart.
The preference is portable, like the other display ones.
2026-08-28 08:25:02 +02:00
rouggy 08d316b1e0 feat(cluster): a SOTA column, and a spot click that credits the summit
The SOTA feeds put the summit in the spot's own text, so there is nothing to
poll: a regex on the comment gives the reference, and clicking the spot writes
SOTA@<ref> into the award references the way a park already wrote POTA@<ref>.

The regex is narrow on purpose — association/region-NNN, anchored — because
POTA (US-4475), WWFF (DLFF-0001) and portable callsigns (DL/SP9DPM/P) share
that field and none of them is a summit. Pinned by a table test.

Off by default: the reference is only there on the summit feeds, and an empty
column for every operator who does not chase them is worse than a checkbox.
2026-08-27 21:33:17 +02:00
rouggy b8796369ba fix(amp): the KPA was missing from the status-bar chip
The chip read a.spe ?? a.acom ?? a.pgxl, so a KPA fell through to the
not-connected fallback: red on a working amplifier, and a click that opened the
settings instead of switching to STANDBY. Reported from a KPA500 whose panel
showed band, SWR and temperature at the same time.

The card's offline text also borrowed the Acom's string, so a KPA that dropped
its link announced itself as an Acom.
2026-08-27 18:55:55 +02:00
rouggy 2594697e00 fix(cat): the chip says what YOU called the radio
It showed what the radio calls itself over CAT — 'Kenwood (911)' for a K3,
a bare 'CAT' for a Flex that reports nothing useful. That is the answer to
a question nobody asks once the rig has a name in the list, and it made
the two radios look alike in the one place they have to be told apart.

The name the operator typed comes first now, on the chip and in the menu,
and falls back to the radio's own identity when they typed none. The model
identity is still in the tooltip, where it answers 'what is actually
connected' without taking the place of 'which of my rigs is this'.
2026-08-27 00:42:26 +02:00
rouggy cf5efea3e4 fix(cat): the chip opens the radio menu with one radio too
It only became a menu from two radios, on the reasoning that a single
radio has nothing to switch to. That hides the feature from precisely the
operator who has not made a second radio yet — the click lands on a
settings dialog they did not ask for, which is what happened on the first
try. With one radio the menu now shows that radio and 'Add a radio…'.

Right-click still goes straight to the CAT settings.
2026-08-27 00:32:00 +02:00
rouggy 4689505fb9 feat(cat): several radios, switched from the status bar
The answer to 'I have two rigs' was 'make two profiles', which is a heavy
instrument for a light question: a profile carries the whole station —
logbook, callsign, awards, cluster, macros — so switching one to change
which radio is connected takes all of that with it, including a change of
logbook when the profiles point at different databases.

Radios are a list now, the way amplifiers already are. The CAT chip in the
status bar opens it, one click connects the chosen rig, and nothing else
about the station moves.

The storage keeps the ACTIVE radio in the same settings keys everything
already reads (cat.backend, cat.icom_port, …), and switching writes the
chosen entry into them and reloads the link. So the consoles, the CAT
sharing, the band-follow and every panel see exactly what they saw before
and needed no change at all — the list is a second store beside the
configuration, not a replacement for it.

Details that matter more than they look:
  - An operator who has run one rig for a year opens the list and finds it
    there, because a missing list reads as the CURRENT settings rather
    than as nothing. It is written on the first save, not on the first
    read.
  - Editing the CAT panel updates the active entry, or an edit made before
    switching away would be lost on the way back.
  - A new radio starts as a COPY of the current one: a second rig is
    usually the same shape with one port changed, and an empty form is a
    form to fill in twice.
  - Switching saves the panel first, so edits to the radio being left
    behind are kept.
  - The chip only becomes a menu with more than one radio; with one it
    opens the CAT settings exactly as it always did.
2026-08-27 00:22:19 +02:00
rouggy 98c49f6bbc feat(tci): a control console for the SunSDR
TCI already carries the frequency, the mode, the meters and now the audio.
It also carries everything else about the radio — and OpsLog was logging
most of it once as '(unhandled once)' and throwing it away. The console is
mostly a place to put what was already arriving.

That makes it the cheapest panel here, and it is worth saying why. A K3
console costs a command and a reply for every value it shows, which is why
it reads its settings in a rotation and its meters only while on screen.
TCI PUSHES: the radio announces its drive, its filters, its noise blanker
and the rest on connect, and again whenever any of them changes —
including when the operator changes them in ExpertSDR3's own window, which
this panel therefore follows without asking anything.

What it drives: drive and tune drive, mic gain, TUNE, volume, mute,
squelch and its threshold, NB, NR, ANF, APF, AGC speed, the passband, RIT
and XIT with their offsets, and the VFO lock. The S-meter is a real dBm
reading, so its S units are arithmetic rather than the calibration guess a
K3's meter needs.

Setters never update the cached state. The radio answers with the new
value, and taking its word is what keeps the panel honest when a setting
is refused, clamped, or changed at the radio a second later — the one
exception being a slider mid-drag, held for 900 ms so it is not dragged
back by its own echo.

Capped width and centred, like the other consoles. Also offered as a
docked pane — and the Elecraft console is offered there too now: App has
always had that pane, Settings simply never listed it.
2026-08-26 18:31:44 +02:00
rouggy e8dfa0eaaf fix(ui): stop Preferences redrawing with the main window; unclip the dropdowns
TWO FAULTS, ONE SYMPTOM — 'it refreshes ten times a second and the
buttons cannot be pressed'.

Preferences is a child of the main view, so every cluster spot, CAT push
and decode re-rendered the entire panel. On a busy evening that is
several times a second, and the panel is large enough that the rebuild
outlasts the gap between them: buttons missed their clicks because the
element under the pointer was replaced between the press and the release.
It is memoised now, and the callbacks App hands it hold their identity —
without that the memo compares unequal every time and buys nothing.

The dropdown menu was an absolutely-positioned child, so it was clipped
by whichever scrolling or overflow-hidden box it sat in: the satellite
list showed one entry of eight. It is portalled to the body now,
positioned from the field's rectangle, re-measured while open, and opens
upward when the field is near the bottom of the screen — which is exactly
where these fields tend to be.
2026-08-25 20:04:07 +02:00
rouggy 67c85be23c fix(sat,rda): the dropdown reads the live list, and the RDA buttons answer
SATELLITES: the field read the list once, when the details panel first
mounted — which is at startup. A list saved in Preferences afterwards
therefore did nothing until a restart. It comes from App now, which
already reloads the lists when Preferences close.

RDA COMPARE: a failed comparison wrote its error into the message beside
the FILL DISTRICTS button, a row above — the error appeared under a
button nobody had pressed while the one that had been pressed showed
nothing, which reads as 'the button does nothing'. It has its own message
now, and reports the two results that look like silence: no disagreement
at all, and no Russian contacts to compare. Both buttons say what they
are doing while they do it, and the comparison logs before it starts
reading — on a remote MySQL that read is seconds of quiet.
2026-08-25 19:53:30 +02:00
rouggy 1e87081058 feat(lists): a satellite list, offered on the entry form
SAT_NAME is compared character for character by the awards and by LoTW:
AO-91 and AO91 are two different satellites to everything downstream, and
typing it afresh on every pass is how one of them ends up in a log. So
the station keeps its own list (Preferences → Lists → Satellites) and the
field offers it, alphabetically — while still accepting anything typed,
because a bird worked once and never added to the list must not be
impossible to log.

Not seeded with a shipped list of two dozen birds: an empty list means
this station does not work satellites, and filling the dropdown with
names nobody here has heard makes the field harder to use, not easier.

Also fixes the RDA district comparison: its conflict list was capped at
about six visible rows of a list holding up to two hundred, in a panel
that would not scroll to show the rest, and nothing in it could be acted
on. Taller, scrolling, and a callsign opens the contact.
2026-08-25 19:30:29 +02:00
rouggy 27d0952d01 feat(cluster): the spot list size is a setting
The list is a ring buffer that held a thousand spots, and on a busy
evening a thousand arrive in a couple of minutes. So the buffer decided
how long a spot lived, and the spot LIFETIME setting never got the chance
to expire anything: fifteen minutes meant nothing when the oldest spot
was pushed out after two.

Now settable (Preferences → Cluster, beside the lifetime, since between
them they decide the same thing), 100 to 10 000. The ceiling is a real
limit rather than a round number: every spot is matched against the
worked index and the alert rules, and is a row the cluster grid and every
open band map re-render.
2026-08-24 19:15:32 +02:00
rouggy 4e9b1eebe9 feat(cluster): single click fills the call, double click works the spot
Reading the cluster means running down dozens of lines, and a single
click did the whole job — QSY, mode, callsign — so every line looked at
dragged the radio with it. One stray click while reading took the
operator off the station they were working.

Looking and going are two different intentions, so they are two gestures
now. The band map is unchanged: clicking a spot there is already a
deliberate act, not a way of reading a list.
2026-08-24 19:08:33 +02:00
rouggy 0848c275d2 fix(yaesu): mic gain scale, 200 W models, and a NAR button that did nothing
Reported from a real FTDX101, and each one is a different kind of wrong.

MIC GAIN was read and written through the 0-255 scale the audio gains
use, but the CAT reference gives MG000-100. A rig set to 80 therefore
showed 38, and moving the slider sent 204 — outside the range the radio
accepts, so it refused the command and the slider sprang back. That
snap-back was the symptom; the scale was the cause.

POWER was capped at 100 W by the slider, not by the radio. There is no
CAT command for 'how much power can you make', so the ceiling comes from
the model name, with the rig believed if it ever reports more than the
table expects — it has just proved what it can do.

NAR is the narrow IF filter, and on a rig that does not implement NA the
button showed a state the radio never gave and did nothing when pressed,
which reads as a fault in the radio. Now shown only when the rig answers,
with a tooltip saying what it is.

Also: the frequency readout steps the Hz digits under the wheel, not just
the kHz ones. Zero-beating a CW signal is a few tens of Hz and it was the
one move the display would not make. And the split chaser is CW-only —
its marker comes from a CW skimmer.
2026-08-24 17:59:13 +02:00
rouggy ea0789b191 feat(entry): type a frequency or a band into the call field
An operator reading a cluster page sees 28500 and wants to be there. The
hands are on the callsign field; reaching for the frequency box, or the
mouse, is the part that loses the station. So the call field takes a bare
number on Enter: kHz for a frequency, or a band on its own.

Ambiguity resolves to the band on purpose -- 40 is 40 m, not 40 kHz --
because neither of those kHz values is anywhere a radio tunes, and it
reads the way an operator says it. Anything with a letter in it is a
callsign and behaves exactly as before; a number that is neither a band
nor a plausible frequency does nothing at all.

Bands go through the ordinary band change, so the antennas, the per-band
power table and the outbound integrations hear about it as they would
from the dropdown. Also adds 4 m to the QSY table, so a band that can now
be typed is a band that has a frequency to go to.
2026-08-24 17:42:42 +02:00
rouggy 60dad353ce feat(elecraft): a K3/K4 console — power, volume, S-meter, SWR, MOX, tune
The Elecraft backend already existed; what was missing was somewhere to
operate the radio from. This adds the panel, on the Kenwood-dialect
client the K3 already speaks, in a tab of its own beside the Yaesu and
Icom consoles.

Scope is the six controls asked for and nothing else. Every extra command
added without a radio to test it against is a control that may or may not
do what its label says, and a K3 exposes dozens.

No K3 was available while writing this, so the two halves are treated
differently. The setters are the commands the reference documents
unambiguously and whose effect is visible and reversible (PC, AG,
TX/RX). The meters are the opposite: their scaling differs by model and
firmware, so the raw answers are logged next to the power setting, the
panel says the scaling is provisional, and an unmeasured SWR shows as
'—' rather than as a perfect 1.0 — a good match on an antenna nobody
measured is the reading that costs a radio.

ATU tune sends SWT20 (the K3 front-panel tap) and logs exactly what it
sent, so a wrong mapping names itself instead of leaving an operator
guessing which button OpsLog pressed.
2026-08-24 14:09:28 +02:00
rouggy 803f4dc4ed feat(hamlog): read confirmations back from a HAMLOG.online ADIF
Their agent protocol has no download verb, so the return path is a file
exported from their site. Running that file through the ordinary ADIF
import is the wrong tool and does real damage: it matches on callsign +
UTC minute + band + mode, their export is rebuilt from their own
database and rarely agrees to the minute, and 'update duplicates' then
inserts everything that failed to match -- several hundred copies of
contacts already in the log.

This path matches only. It stamps the confirmation on QSOs it finds,
falls back to the mode-CLASS key for the modes their export renames, and
REPORTS what it could not match instead of adding it: an unmatched
confirmation is a question about the log, not a contact to create.

Also logs and reports how many rows a delete actually removed -- silence
there made a delete that did nothing indistinguishable from one that
worked.
2026-08-23 15:32:41 +02:00
rouggy c7140fd639 feat(export): filtered-view export can keep the OpsLog fields too
Same reasoning as the selected-rows export: ExportADIFFiltered was called
with includeAppFields=false, so a filtered export lost the award
references. A second entry, next to the standard one.
2026-08-23 15:01:32 +02:00
rouggy c887b77b5f feat(export): right-click export can keep the OpsLog fields
The right-click export always called ExportADIFSelected with
includeAppFields=false, so it wrote strictly standard ADIF. That is the
right default when the file is going to another logger, but it silently
drops every APP_ tag -- and an award reference such as RDA@KR-04 lives in
APP_OPSLOG_AWARDREFS and nowhere else. Backing up a selection, or moving
it to another OpsLog, lost the awards.

Adds a second entry rather than a flag on the first: the two exports
answer different questions, and a checkbox in a context menu would have
to be read before every export.
2026-08-23 14:58:01 +02:00
rouggy cd557acf81 feat(hamlog): send to HAMLOG.online from the right-click menu
The upload targets in the QSO context menu gain HAMLOG.online, so a selection
goes there the same way it goes to QRZ or LoTW.

The confirmation toast now names the service from a map rather than a chain of
ternaries that stopped after three: hrdlog, eqsl and hamlog were echoed as their
internal ids, which is not what the operator clicked on.
2026-08-23 12:46:29 +02:00
rouggy ee148732dd feat: HAMLOG.online upload and confirmations, Yaesu antenna, a named port holder
HAMLOG.online is a seventh external service: one API key, one ADIF record per
QSO, immediate / delayed / on-close like the rest. They publish no API
documentation, so the protocol is read from THEIR OWN client — the HAMLOG Agent
(github.com/hamlogonline/Agent), which is the authoritative source short of
asking them:

	POST https://hamlog.online/api/agent/
	{"ADIFADD": {"APIKEY": k, "ADIFDATA": record}}   → {"STATUS":"OK"}
	{"KEYSTATUS": {"APIKEY": k}}                     → {"STATUS":"OK","CALLSIGN":…}

Success is STATUS == OK, not "no ERROR field": their failure carries ERROR and
no STATUS, and reading an unknown reply — a proxy page, a maintenance notice —
as an acceptance is how a contact goes missing without anyone noticing.

KEYSTATUS buys something no other service here offers: the key can be checked
BEFORE the first QSO, and the answer names the account. A key pasted from
another callsign is caught in the settings panel rather than through a week of
silent refusals.

Their confirmations are also an award source now, ticked like LoTW rather than
expressed through "custom". It reads the ADIF extras, not a column: the standard
names a field for hamlog.EU and none for hamlog.ONLINE, and borrowing the other
site's field would write a falsehood into every exported log. Three plausible
key names from their own export are accepted too, so nobody has to rename a
column by hand after an export.

Yaesu gains antenna selection (AN), remembered per band — the antenna picked on
a band comes back with it, with no table to fill in anywhere. Rigs with one
socket never answer AN and never show the row; the log says which case it is.

And a serial port that is refused now names its likely holder. OmniRig stays
resident once activated and keeps the port of the rig configured in it, so a
native backend never gets it — "Serial port busy" alone accused nobody, and an
FTDX10 spent a morning being blamed for it.
2026-08-23 12:18:27 +02:00
rouggy 405b0e24cf feat: network audio into the QSO recorder, and a Yaesu that stays on its VFO
An Icom reached over the LAN streams its receive audio through the Icom
protocol, and Windows sees no sound card for it at all — the audio settings
could only offer the PC's own microphone, so "From radio" had nothing right to
point at and the QSO recorder had nothing to record. The recorder now accepts a
PUSHED source: the decoded stream goes to the speakers and to the recorder
alike, with no virtual cable to set up. Which source it uses follows the CAT
backend, and it is restarted only when that answer changes, so an ordinary
settings save never cuts a recording in half.

OmniRig no longer sends SetSimplexMode to a Yaesu when tuning. It is a silent
no-op on some — an FT-891 logged OK on every spot click while FreqA never
moved — and actively harmful on others. From an FT-2000 log, one QSY:

	Vfo="AB"(0x80)  Split=0x10000 (off)   the operator's state
	Vfo="BA"(0x100) Split=0x8000  (ON)    after SetSimplexMode

The rig-agnostic "receive and transmit HERE, simplex" call turned split on and
moved reception to VFO B. OpsLog then displayed B — reading the radio correctly,
after having moved it itself. Icom is untouched: there the call is the
authoritative one and the direct write is unreliable.

Also:
  - the NEW county badge shows in the entry form itself, inside the field,
    where the operator is deciding whether to call.
  - the basemap buttons clear the zoom controls; Light sat a few pixels from
    the minus button and was being clicked by mistake.
  - French cluster status: DÉJÀ CTC reads DÉJÀ QSO.
2026-08-23 10:43:11 +02:00
rouggy f50806fbd9 feat: panadapter spots that read, a CI-V link that survives, no auto-call
Panadapter spots are now worth reading. The comment carries the spotter, the
entity and the status in DXHunter's own shape — "CQ up 2 [F4BPO] [Franz Josef
Land] [New Slot]" — which needed two things nothing documents: SmartSDR splits
its command line on SPACES, so the words ran together until every space became
non-breaking; and it truncates past ~60 characters, so the cluster's own words
are trimmed first and the three brackets always survive. RBN column padding is
collapsed on the way in, or a preserved run of spaces opened a gap wide enough
to push the rest off screen.

"Already worked" means the CALLSIGN is in the log, not the entity: saying it of
a station never contacted was simply wrong. Each status can also be kept off the
panadapter entirely, and the WSJT-X decode spots obey the same switches — the
palette governs the panadapter, not one of the two things that feed it.

And the radio is no longer hammered: a spot whose frequency, colour and comment
are unchanged is not removed and redrawn. A busy skimmer feed re-spots the same
station every few seconds; one two-minute session sent 2128 adds, 88 of them for
a single callsign, and the display did not move a pixel for any of them.

CI-V, from an IC-7850 that kept killing JTDX: a reply the rig sent to another
controller on the same bus is no longer taken for ours, and a set_ptt, set_freq
or set_mode whose acknowledgement goes missing is verified by reading the rig
back instead of being reported as a failure. WSJT-X and JTDX answer a failed
command with a Rig Control Error and drop the link mid-over — 98 keyings, 6 lost
acknowledgements, 2 dropped connections in one session. The check waits 700 ms,
not the poll's 150: the rig has just failed to answer twice because it was
retuning, and a short probe would fail for the same reason.

Auto-call is withdrawn — it duplicated DXHunter, which already answers decodes,
and two programs deciding that from one shack key over each other. The library
is kept whole and dormant; a guard in App.tsx makes sure a stored preference
cannot key a transmitter whose switch no longer exists.

Also:
  - the log rotates while running, not only at startup: the CI-V trace left on
    wrote 416 MB and nothing would have stopped it before the disk did. Closing
    it now releases the crash file too — the runtime keeps its own duplicate.
  - the interface zoom announces itself, with a badge, a click back to 100% and
    a View menu; Ctrl+wheel and Ctrl+0 always worked and nothing said so.
  - no more elastic bounce, and no swipe-to-navigate out of the app.
  - Edit QSO: your own TX power and the contacted station's extended locator
    were saved and written back with no box to set them.
  - FT decodes: continents are a multiple choice; a compound MSHV message that
    answers two stations in one line is recognised as addressed to you.
2026-08-23 01:16:26 +02:00
rouggy cb430a22ee feat: Ultrabeam over USB, Paper QSL from the awards grid, per-role schemas
Ultrabeam on a serial port never worked, and three faults were stacked so
each hid the next:

  - Stop() did not wait for the poll loop, so a stopped client kept the COM
    port. Every later client then failed with "Serial port busy" — the
    program holding it being OpsLog itself.
  - startUltrabeam tore the old client down CONCURRENTLY with starting the
    new one, and "Test connection" built a second client on a port already
    ours. Harmless over TCP, fatal on a port with one owner.
  - A silent serial port returns (0, nil) and bufio retries that a hundred
    times: a 4 s timeout became ~7 minutes of a frozen poll loop logging
    nothing.

The controller then answered at once. Confirmed on hardware: the USB cable
presents TWO COM ports, only the second reaches the controller, and only at
19200 baud — so the speed is pinned in code (an FTDI cable opens at any
speed, and a wrong one is indistinguishable from a dead controller) and the
port field says which one to pick. The first exchange after each connect is
hex-dumped, which separates silence from a wrong baud from a misread frame.

Databases now carry only the tables their role needs. Every target used to
get the whole migration set, so a shared MySQL logbook grew settings and
station_profiles tables nothing ever wrote to — an operator inspecting the
server could not tell which copy was authoritative. Statements are filtered
by role, unknown tables are kept in both (fail-safe), and existing databases
are cleaned once, dropping only EMPTY tables. Settings → Database gains a
Compact button, since SQLite frees pages inside the file and never shrinks it.

Also:
  - Awards: the callsigns behind a cell open the QSL Manager on Paper QSL,
    searched, ready for the card dates.
  - The record button no longer goes missing after an update: whether manual
    recording is possible is a per-profile question that was asked once, at
    startup, before the profile was known.
  - Alert rules and filter presets confirm that they were saved.
  - Spot clicks on the radio panadapter carry the POTA park into F3.
  - The build gate is re-checked wherever the active callsign can change; it
    ran at startup alone, and a fresh install has no callsign then.
2026-08-22 14:07:00 +02:00
rouggy abfed4afa2 chore: release v0.26.4 2026-08-22 08:35:49 +02:00