Commit Graph
621 Commits
Author SHA1 Message Date
rouggy 7ac342e2d4 feat(icom): the speaker choice appears beside the RX-audio option too
Same remembered preference as the console's speaker button, surfaced where
the stream itself is enabled — tick RX audio and decide in the same breath
whether it reaches the speakers. Applied immediately, no save needed.
2026-08-29 20:02:28 +02:00
rouggy b244575937 feat(icom): a speaker toggle on the console itself
Muting the network RX audio took a trip through Settings → Audio → Stop
listening — two panels away from the console the operator is actually using.
A speaker button now sits beside ON/OFF (network only, like them): one click
to listen or mute, the recorder and the voice keyer unaffected, the choice
remembered across reconnects and launches.
2026-08-29 19:49:09 +02:00
rouggy c878742e50 feat(icom): listening to the network audio is a remembered choice
The RX stream and the speakers were welded together: every reconnect and
every launch restarted the monitor, so an operator sitting in the room with
the radio could not have the stream (which the recorder and the voice keyer
need) without also hearing it twice. Stop listening now persists, the Listen
button turns it back on, and reconnects respect the choice.
2026-08-29 19:42:18 +02:00
rouggy 13434ca36c feat(icom): phase 5 — the voice keyer plays to the radio over the LAN
The audio session opens its TX side alongside RX (txenable in the conninfo,
16 kHz both ways), the stream gains a paced sender — 320 samples every 20 ms,
the frame mirroring what the rig itself sends on this socket, IDs swapped —
and the network Icom takes the same NetworkPlayer slot a TCI radio does: pick
'Radio (network audio)' as the To-radio device and the voice keyer needs no
cable and no virtual sound card. PTT brackets the message through the same
code as every other audio path.
2026-08-29 19:34:05 +02:00
rouggy 6345710de5 fix(icom): wait out the rig's session cleanup before redialling
The capture shows the spiral: a session goes deaf on CI-V (28 unanswered
commands, transport alive), the fast-fail rebuilds it, the new session answers
for one second — one state read got through — and is then strangled when the
rig purges the half-open old one. Twenty seconds a lap, no audio throughout,
and only the third or fourth session survives. When the previous session died
less than 90 s after connecting, the redial now pauses 20 s so the purge
happens BEFORE the new session, not on top of it. A long-lived session still
reconnects immediately.
2026-08-29 19:21:55 +02:00
rouggy 2c0158b75c fix(icom): re-open the CI-V flow when the rig goes quiet mid-session
The IC-7760 recurrently stops answering CI-V a few minutes into a network
session while the transport keeps chatting — pings answered, session alive,
commands unanswered — and the only recovery was the 30 s watchdog's full
teardown. At 10 s of CI-V silence the pump now says 'open' again on the
existing stream, which is all it should take if the rig quietly closed the
data flow; the watchdog remains as the backstop. The send counters gain a
mutex since the pump now transmits too.
2026-08-29 16:56:06 +02:00
rouggy 1fba7d2d57 fix(icom): create the audio manager before the CAT link comes up
reloadCAT builds the network audio sink gated on audioMgr, and startup created
the manager AFTER the first reloadCAT: every fresh launch connected with audio
silently off however the option was set, and the operator had to
untick/save/retick it to hear anything. The manager is idle-until-used, so
creating it earlier costs nothing.
2026-08-29 16:47:42 +02:00
rouggy 490941f506 fix(icom): only forward-moving audio packets reach the sink
Every audio packet was delivered as if it were the next 20 ms, duplicates and
late retransmits included. The monitor's capped ring quietly threw the surplus
away, so the speakers sounded fine — but the recorder keeps every sample it is
given, and the file came out longer than the QSO, slowed and stuttering, each
lost-then-resent packet heard twice. trackRxSeq now says whether a packet
advances the stream, and only those are handed on.
2026-08-29 16:43:41 +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 b246c4d10a fix(icom): power in watts on the 7760, and Listening restarts clean
The power meter applies Icom's own meter calibration (raw 143 = half
deflection, 213 = full scale) anchored against the real radio: a measured
100 W sits at half deflection of the 7760's 250 W face — the linear guess
showed it as 140. The RF power slider says watts on a 200 W rig instead of a
percentage the operator has to convert.

And the Listening button, pressed while a network Icom is connected, restarts
the monitor render-only: it used to open a USB capture from the From-radio
device as well, and that second producer interleaved with the network pushes
chopped the audio to pieces.
2026-08-29 16:02:15 +02:00
rouggy 6d4a110949 fix(cat): the settings panel opens on the radio that is actually active
The loader looked for an 'active' flag the radio list has never carried, so
reopening the panel always landed on the first entry — Radio 1's name over
whichever radio's settings were live, and every field edit (MY_RIG included)
silently rewriting the wrong entry. The backend is the only one who knows
which radio is on the air; ask it.
2026-08-29 15:52:37 +02:00
rouggy ffbbff80d6 feat(icom): a DATA button, native PSK, real watts — and the audio codec
The console gains a DATA mode button (USB-D, what FT8 and friends ride on);
the PSK button now maps to the rigs' native PSK mode (0x12) instead of erroring
as an unknown mode — rigs without one NAK it, exactly as RS-BA1's button does
there. The power meter shows real watts on the IC-7760, whose meter face runs
to 250 W where a 100 W rig's percentage was the watts. The clickable S-meter
gets a pointer cursor so it looks like what it is.

Network audio: rxcodec 0x04 — the experiments on the real 7760 settle the
codec table (0x10 = 16-bit stereo, 0x02 = 8-bit mono, both heard as garble),
and 0x04 is the 16-bit mono the playback path actually plays.
2026-08-29 15:48:26 +02:00
rouggy bd2edf6624 fix(icom): verify the data flag after a mode set, and correct it with 0x26
The IC-7760 acknowledges the 1A 06 data-mode command and stays in USB-D
anyway, which left the operator unable to get back to plain USB from the
console at all. After every mode set the flag is read back; on a mismatch it
is said again with 0x26 — mode, data flag and filter in one frame, the form
the newer rigs actually honour. Only on a mismatch, so rigs that predate 0x26
never see the command.
2026-08-29 15:36:55 +02:00
rouggy 89e239d83f fix(icom): decode the network RX audio as what the rig actually sends
The first real radio on the 50003 stream (an IC-7760) settles the two guesses
the experimental audio path shipped with. The payload starts at 0x18 — the
packet carries a big-endian payload length at 0x14 (0x500 on every packet) —
not at 0x16, which swallowed two header bytes into the PCM and laid a 50 Hz
click track under everything. And rxcodec 0x10 asks for TWO-channel LPCM,
which the mono playback path rendered as double-speed garble; 0x02 asks for
the one channel the monitor plays.
2026-08-29 15:32:44 +02:00
rouggy 0e48ad7bb2 fix(icom): the IC-7760's attenuator is the stepped 6/12/18 dB kind
Confirmed on a real one. It was falling into the single-20 dB default, whose
one button the rig NAKs.
2026-08-29 15:29:14 +02:00
rouggy 6f126802cd feat(icom): the IC-7760 joins the model list
CI-V default address B2h, per the radio's own menu. Both places that know a
model: the settings dropdown and civ.ModelName.
2026-08-29 15:02:23 +02:00
rouggy 704b614c38 fix(elecraft): calibrate the S-meter, and stop the SWR spike lingering
The S-unit mapping was provisional, waiting for a real radio. One arrived:
side by side with a K3's own display, raw 5 reads S7 and raw 9 reads S9+20,
so S9 sits near raw 6.5 — not 9 — and each raw step above it is worth ~8 dB.
The old scale showed everything two S-units low.

The SWR meter borrowed the power meter's needle inertia — hold the peak, then
close a quarter of the gap per poll. The K3 throws a brief SWR spike as an FT8
frame ends, and that decay turned one bad sample into twelve seconds of red on
the next transmission. SWR now holds the peak for the same 1.5 s and then
snaps back to the live reading: it is a warning light, not a needle.
2026-08-29 13:52:40 +02:00
rouggy dbd2fff121 chore(changelog): the watchlist block becomes 0.27.0
A new tab, a new file format carried over from another program, and a ClubLog
integration are a minor version, not a patch.
2026-08-29 02:56:28 +02:00
rouggy dc6da67cbf chore(changelog): the watchlist headlines 0.26.24
The block's one genuinely new feature opens it — with the ClubLog enrichment,
which had not made it into the entry yet — instead of sitting seventh between
an Elecraft button row and a bulk-edit field.
2026-08-29 02:55:16 +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 c9e98d5fdd feat(watchlist): exact match by default, * for a family
The prefix matching ported from DXHunter swallowed too much: a three-letter
special-event entry N8W lit up NEEDED for N8WCR, a different station entirely.
The implicit becomes explicit — a bare entry matches exactly that call, a
trailing star makes it a family: VK9* catches every VK9…, RI0SP* the
expedition's portable forms. A star anywhere else (or alone) is refused at Add
rather than silently matching nothing. Same rule in the backend Match and the
tab's own matcher, pinned by test.
2026-08-29 01:25:34 +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 284ee4ba7c feat(bulk): My DXCC / My CQ zone / My ITU zone
The My-station group covered nineteen fields and not the three numeric ones.
They take their own integer path rather than the generic text setter: the
columns are nullable integers, and while SQLite would coerce '14' quietly, a
shared MySQL logbook would not — and empty must become NULL, never ''. Bounds
checked (DXCC <1000, CQ 1-40, ITU 1-90) so a slip cannot stamp zone 400 across
a thousand rows.
2026-08-29 00:13:47 +02:00
rouggy 10ef984962 feat(k3): align RIT/XIT and the power step with the other consoles
'None of the consoles look alike' is the complaint ShiftRow exists to answer,
and the Elecraft panel was still driving its offset with a row of nudge buttons.
It now uses the shared control — ±, wheel, typed value — plus the same
Ctrl+←/→ 10 Hz clarifier the Icom and TCI panels have. The K3 keeps ONE offset
for RIT and XIT, so both rows show it, exactly like the Icom's.

The backend gains the absolute setter the shared control speaks
(SetKenwoodRITOffset, funnelled through the same RO write as the nudge), and
the power slider moves in 1 W steps — nobody asks a K3 for 'between 10 and 15
watts' in fives.

Also per review: the chase switches are labelled 'Chase POTA' / 'Chase SOTA',
matching 'Chase new grids' beside them.
2026-08-28 23:56:49 +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 7e7ad50f60 feat(k3): a mode row on the Elecraft console — CW/USB/LSB/DATA/DATA-RTTY
The report: switching the K3 to a data mode 'does not work for FT8 — the K3
does not go into DATA'. The mode digit is only half the answer on that radio:
MD6 keeps whatever DT submode the previous session left, and with FSK D still
armed the rig keys FT8 with no rear-audio modulation.

The panel buttons say the whole thing: DATA is MD6+DT0 (DATA A, the soundcard
path), DATA RTTY is MD6+DT2 (FSK D). The DT submode is also read on the
settings beat — Elecraft only, a plain Kenwood would '?;' it — so the active
DATA button follows what the rig is actually in, front-panel changes included.
A dedicated SetKenwoodPanelMode rather than the logger's SetMode: a panel
button is the operator saying exactly what the rig should do, not an ADIF mode
to be mapped through preferences.
2026-08-28 23:35:25 +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 c222468021 style(cluster): give NEW SLOT its own colour
The whole new-* family shared the warning amber while the NEW PFX marker sits in
caution yellow — two different facts, two near-identical colours in one cell.
NEW SLOT now takes aqua (chart-2), the same story the panadapter palette already
tells: its default for new-slot has been cyan all along.
2026-08-28 23:24:06 +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 8acfda4e42 fix(tci): resubscribe to the meters at 'ready', and log what comes back
The report from a real SunSDR: transmit power and SWR still empty after the
sensors fix. The subscription went out once, at connect — while ExpertSDR3 is
still streaming its initial state dump, exactly where a unidirectional control
command can be ignored. It is now renewed every time the server says 'ready'.

The sensor messages also join the logged-on-arrival set, capped like the rest:
the next log will say whether TX_SENSORS ever arrives, which is the question —
a radio that never sends it (AetherSDR may not) and a frame that arrived and
was dropped leave the same blank meters and need opposite fixes.
2026-08-28 22:51:25 +02:00
rouggy f0f9898f7c feat(bandmap): draw the band plan of the operator's IARU region
The band maps carried one hard-coded table — Region 1's — so a US operator's
40 m stopped at 7200 with stations spotted at 7250 hanging past the top of the
map, and the SSB wash started 25 kHz early. The region is a station-level fact
stated once (Settings → General, portable pref); lib/bandplan owns the three
tables and publishes changes to the open maps the way the distance unit reaches
the grids.

The tables stay deliberately coarse: a band map's wash is context, not a
regulatory chart, so only the differences an operator notices are split out —
Region 2's 80 m to 4000 and 40 m to 7300 (phone from 7125), Region 3's 80 m to
3900, the wider VHF/UHF allocations. National fine print does not belong in a
background tint.
2026-08-28 21:55:37 +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 de3a115ca6 chore(changelog): move the post-release entries into a 0.26.23 block
The Station column, the Label Designer and the label printing all landed after
v0.26.22 was tagged; the .22 block is byte-identical to the tag again.
2026-08-28 21:31:09 +02:00
rouggy 1d7633484b fix(labels): the manager's address, not the DX's — and one PDF, just opened
Three corrections from the first real session. Routed via a manager, the box
was prefilled with the DX's own address — the one thing that must not go on
that envelope. It now starts empty and the QRZ fetch fills in the MANAGER's;
switching the routing recomputes the box unless the operator has typed in it,
because a hand-checked address is not the app's to replace.

A fetch that came back with no street was overwriting a reviewed address with a
bare country — the cty.dat fallback dressed as an answer. It now refuses to
touch the box and says nothing was found. And the prefill no longer stacks the
same town three times (address + QTH + country all carrying it).

The output is ONE PDF for the whole session, each page at its own label size,
written to the temp dir and opened straight in the viewer — no save dialog: the
file is a print run, not a document to keep.
2026-08-28 20:07:36 +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 3dc31697cd feat(lotw): name the station callsign on each downloaded confirmation
With 'All my callsigns' the report spans F4BPO, F4BPO/P and TM2Q at once, and a
list mixing them says nothing about which one a confirmation belongs to — which
is the question that option creates.

The column appears only when the rows actually carry a station: the other
services return one, and an empty column for them would be noise.
2026-08-28 17:43:05 +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 47ddbed665 fix(tci): send the spot colour as an unsigned ARGB integer
Expert Electronics' own document gives the command whole:

    SPOT:RN6LHF,CW,7100000,16711680,ANY_TEXT;

16711680 is 0x00FF0000 — positive. This backend sent the same field as a SIGNED
32-bit value, from a third-party example: with the alpha byte set to FF for
opacity, 0xFFFFA500 becomes -22336, and a spot whose colour ExpertSDR cannot
read is dropped without a reply or an error.

Reported on ExpertSDR3 1.3, which speaks TCI 2.x — the protocol version was
never the problem, and my first reading of that report was wrong.

The first three spots are now logged verbatim: an ignored spot leaves no trace
whatsoever, so that line is the only evidence of what went out.
2026-08-28 16:23:32 +02:00
rouggy fd7ae77a61 fix(tci): subscribe to the radio's meters; fix the new-spot count; explain an SMTP refusal
TCI meters: the S-meter came only from RX_SMETER and the transmit meters from
TX_POWER / TX_SWR — commands ExpertSDR3 does not send. The protocol's answer is
a subscription (RX_SENSORS_ENABLE / TX_SENSORS_ENABLE, §4.4 of the TCI PDF),
after which the radio pushes RX_CHANNEL_SENSORS and TX_SENSORS. Nobody had
asked, so the console's meters sat empty in RX and in TX while everything else
worked.

Cluster: the held-spot counter looked for the row it froze on. A station spotted
again REPLACES its row, so that row vanishes in the ordinary course of things
and the count fell through to 'everything is new' — 4, 5, then 500. Counted by
timestamp now, which survives both the replacement and the ring buffer.

SMTP: '535 5.7.139 basic authentication is disabled' is a policy, not a typo.
The message now says so, and says what actually helps, without claiming a policy
when the server simply rejected the password.
2026-08-28 15:29:44 +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 f7b9aa2181 fix(lotw): show TQSL's account of a refused upload, not just the error
UploadLoTW returns both a terse error ('no QSOs processed') and a Message
carrying TQSL's own lines — '17 QSO records were already uploaded', 'N QSO
records are out of date range'. The caller took the error whenever there was
one, which is precisely the half that explains nothing: the operator saw a red
line naming a condition with three unrelated causes and no way to tell which.
2026-08-28 13:10:09 +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 8bc4ed68d4 fix(window): place the window in absolute desktop coordinates
Wails saves and restores in two different coordinate systems. WindowGetPosition
returns GetWindowRect — absolute. WindowSetPosition is winc's ControlBase.SetPos,
which adds the CURRENT monitor's work-area origin:

    w32.SetWindowPos(hwnd, HWND_TOP, int(info.RcWork.Left)+x, ...)

On the primary monitor that origin is 0 and nothing shows. With a monitor to the
LEFT it compounds: a station with its left screen at x=-3840 saved -3844,
reopened there, had -3840 added, and stored -7684 — then -11524, one screen
further out at every launch, until the window was off the desktop entirely.

So the placement is done here, with SetWindowPos and no offset, falling back to
the toolkit when the handle cannot be found. Compact mode had the same pairing
and is fixed with it.
2026-08-28 12:48:03 +02:00
rouggy d534825e92 fix(tci): pick the sideband from the frequency just commanded
Clicking a spot sets the frequency, then the mode 150 ms later, and SetMode
chose USB or LSB from t.freqA — the frequency the radio had ECHOED. From 7 MHz
onto a 14 MHz spot that echo has often not landed, so the sideband was computed
for the band being left: LSB on 20 m. Clicking the same spot again, the echo
having arrived, gave USB. Reported from a SunSDR as 'the frequency is right, the
mode is wrong until I click twice'.

SetFrequency now records what was asked for and SetMode prefers it until the
radio's own echo confirms the move.

The server's 'protocol:' announcement is also kept instead of being filed under
unhandled: it names the ExpertSDR and its TCI version, and panorama spots need
TCI 1.5 — older servers accept the spot commands and silently drop them, which
is indistinguishable from a broken logger. Now it says so.
2026-08-28 12:42:05 +02:00
rouggy 08d6492df1 fix(bandmap): say WHOSE 'Worked' it is — the entity's, not the callsign's
The status comes from spotEntityStatus: it means the country has been worked on
this band and mode. The label said 'Worked (this band + mode already in log)',
which reads as 'I have had this station' — so a spot the cluster called NEW PFX
looked like a contradiction, when the two were answering different questions
about different subjects.

The callsign-level fact has its own marker and its own words ('Callsign already
worked'), so the two are now told apart by reading them.
2026-08-28 12:22:23 +02:00
rouggy 3bb92f79b2 fix(bandmap): name the new prefix and new grid in the tooltip
The strip deliberately shows three markers of five — the pill is 22 px and a
fourth segment makes it a colour code nobody reads. But the tooltip has room and
was filtering them out too, so the same spot read NEW PFX in the cluster and
'Worked (this band + mode already in log)' on the map. Both were true: the
entity is worked on that slot and the WPX prefix never has been. Neither view
said so.
2026-08-28 12:17:09 +02:00