Statistics — the activity chart ignored the period selector: picking a year up
top left it showing the last 7 days, two controls contradicting each other. The
buttons were four fixed rolling windows anchored on the newest QSO, not
granularities. They now choose the BUCKET SIZE over the selected period, with an
Auto default and a step-up when a bucket would be too fine (the backend drops a
series past 750 buckets rather than ship 6000 unreadable bars). The hour-of-day
histogram covered a single day, too little to read anything from; it moves to
its own card with a weekday view, over the whole period.
Band map — the CW/data/phone sub-bands were shaded with the STATUS tokens, the
same amber that means "new band" on the pills drawn on top of them. A sub-band
is an identity, not a state: categorical hues now, validated in both themes
(violet failed on the dark steps — 1.9 ΔE from blue for a protan reader) and
added to the legend, since identity must never be colour-alone.
Frameless window — the OS title bar was a dead 32px band above a window that
already has its own. Minimise/maximise/close move into the app header, which
carries the drag region and double-click-to-maximise. The drag opt-out is by
ROLE in CSS, so a future button in that bar keeps working without anyone
remembering the rule.
Fixes:
- Saving settings froze the window while a device was slow: restarting a link
waits for its poll goroutine, which can sit 45 s inside an uninterruptible COM
Connect when another program holds the rig. The restart is now off the UI
path; a slow one is logged.
- Multi-screen: a MAXIMISED window was never repositioned, so it came back on
the primary screen every launch. The corner is now recorded even when
maximised (it names the monitor) and re-applied while still hidden.
- The Callsign field is marked out at rest — operators were typing the call into
Name, which sat beside it and looked identical.
- QSL dates get a real picker (native, for the OS calendar and locale order); a
malformed old value stays in a text box rather than vanishing behind an empty
one.
Also: a Support OpsLog entry in Help, and a WinKeyer protocol trace. The WK2
report (one element then a ten-second pause, sidetone that will not switch off)
is NOT fixed here: the WK1/WK2/WK3 command sets differ and a guessed fix would
break the operators it works for today. The trace logs every byte with the
command name and spells out the firmware family, which is what will settle it.
The Main-tab pane and the full Recent QSOs tab are the same component sharing
one storage key, but the pane is about half as wide — so it wants fewer and
narrower columns. Whichever was opened last rewrote the other's widths.
The pane now stores under "mainpane", which also scopes its award-column
visibility and widths. The full tab keeps the historical key, so its layout is
untouched; the pane starts from defaults once.
1. A language change rebuilt the column defs, which sets the anti-clobber
guard, but the effect that clears it listened to only two of the rebuild's
causes. Guard stuck on → every column change was silently discarded for the
rest of the session. Now keyed on the rebuilt defs themselves, so any future
cause is covered.
2. The cluster grid had no guard at all: the same rebuild wrote its defaults
over the saved layout, in the cache AND the database. Unrecoverable.
3. Worked-before and cluster read their state before the active profile was
known, so they looked up the unscoped cache key (always a miss) and then
saved under the scoped one. They now wait for the scope and remount on a
profile switch, like the main log.
4. GetUIPref returns an ERROR, not "", while the settings store is not yet
scoped — deliberately distinct from "unset". The frontend read it as "no
preference", rendered defaults, and the first column event overwrote the
good copy. It now retries instead of giving up.
5. The QSL manager had no storageKey, so it shared the main log's layout and
each rewrote the other.
Also: the DB write is debounced (a resize drag fired dozens of writes) with a
flush on close, and a rejected write is retried rather than dropped.
OmniRig reports the VFO pair (AA/AB/BA/BB) on the whole Yaesu range and never
the single-letter form. Only the latter was honoured, so every rig in that
family stayed pinned to VFO A: pressing SUB moved the radio but not OpsLog,
and a QSO worked on SUB was logged on the main VFO's frequency. The first
letter of the pair is the VFO being listened on — Log4OM reads it and gets the
right frequency on the same rigs, which is what showed the data was there.
The enum now wins over the Yaesu Freq==FreqB inference, which is only a
fallback for a rig file that names no VFO at all (the stock FTDX10 one answers
neither VS; nor FR; usably — verified on the air).
Split: the ON flag is still latched to survive a rig file that flips it on its
own, but the latch is now ARMED only after 8 flips in 30 s. A first cut at
3-in-15s was armed by the operator toggling split while testing, imposing the
6 s clearing delay on a radio that did not need it; a misreading file flips a
dozen times in that window untouched, so the two cases separate cleanly.
Distance (km) column added to Recent QSOs and Worked before (shared catalog).
Computed from the QSO's OWN my_grid/my_lat/lon first, falling back to the
current profile's locator: a log spans years and portable outings, so the
station a QSO was made from is not necessarily today's.
Locator: a precise QRZ/HamQTH grid is no longer overwritten by the cty.dat
entity centroid. The lookup runs several times per QSO and the provider gets
2 s; a slow second answer fell back to cty.dat and downgraded JN05JG to JN16
while name and QTH survived (they are only written when non-empty).
The OmniRig diagnostic line now logs what OpsLog concluded, not just what
OmniRig reported. icomnet.go: gofmt alignment only.
- SCP/N+1: new internal/scp downloads the community MASTER.SCP master list and a
docked two-column widget shows, as you type a call, the known calls containing it
(Partial) and the calls one edit away (N+1) — click to fix a busted call. Opt-in
in Settings → General; top-bar toggle; queried debounced on callsign input.
- Flex: OpsLog's GUI-client detection was too loose and could bind to "SmartSDR CAT"
(or DAX) — both carry "smartsdr" in the program name — instead of the real GUI
client, making SmartSDR CAT drop and reconnect in a loop while OpsLog was open.
Now it binds only to a real SmartSDR/Maestro GUI client (e.g. a FLEX-8600M's
integrated screen) and excludes cat/dax; dropped the risky empty-program fallback.
- Telemetry: on a fresh install the callsign isn't set at launch, so the once-a-day
heartbeat recorded the machine UUID. Now it waits (~10 min) for the operator to
enter their callsign before sending, falling back to the UUID only if none appears.
For an entity resolved only via cty.dat (e.g. a French call not on QRZ/HamQTH),
the provider block sets a coarse country-centroid grid. The worked-before backfill
now overrides that with the precise locator from the last QSO with this call, and
takes that QSO's lat/lon (derived from the grid when the record stored none) so
the map and saved record stay consistent. A real provider grid still wins.
Moved the (unreleased) worked-before backfill entry into a new 0.21.2 changelog
block, since 0.21.1 is already out.
When a callsign resolves only to cty.dat (not on QRZ/HamQTH, or no lookup service
configured) — or the lookup errors — enrich the entry from the most recent QSO
already in the log with that call. Fills ONLY the fields the provider left empty
and the operator hasn't edited (name, QTH, grid, country, address, state, county,
lat/lon, zones, continent, email, QSL-via), so a real QRZ/HamQTH hit is never
overridden. Uses the worked-before entries (qso_date DESC, [0] = latest) via a
live ref so it works regardless of the lookup/worked-before debounce ordering.
CSS `zoom` re-lays-out and pixel-rounds each element, which opened ~1px seams
between map tiles (a faint grid over the map when zoomed). Switch the persistent
UI zoom to `transform: scale(z)` from the app root's top-left, with the root
counter-sized to (100/z)vw × (100/z)vh so it reflows to fill and scales back to
the window. transform scales the subtree as one composited layer, so tiles stay
seamless. Removed the now-unneeded tile-overlap CSS hack.
Under our CSS zoom the map tiles scale by a fractional factor, opening ~1px gaps
between adjacent tiles that read as a faint grid over the map. Tag the document
with data-uizoom and, only while zoomed, enlarge each 256px tile by 1px so
neighbours overlap and cover the seams. The 100% view is untouched.
CSS `zoom` doesn't rescale viewport units, so the 100vh app root left an empty
strip below the UI when zoomed under 100%. Counter-size the app root to
(100/z)vw × (100/z)vh so that, after the zoom scales it back down, it exactly
fills the window again. Zoom still applies to the whole document so portalled
menus/modals scale with it.
- Zoom: our own Ctrl+wheel zoom (CSS zoom on the root, 50–250%), persisted in
localStorage and restored at startup; Ctrl+0 resets. Replaces the non-persistent
native WebView2 zoom. The freq-digit wheel now ignores Ctrl so it passes through
to zoom.
- Award column widths: they were stripped from AG Grid's saved column state (that
strip fixes a visibility desync) which also dropped their width. Now persisted
separately per award code (localStorage + portable DB copy) and re-applied on
rebuild/reopen.
- CW keyer widget: macros padded to 9 (F1–F9 slot) and empty macros hidden like
the voice keyer, with the F-number kept tied to the real macro index so the
F-key shortcuts still line up. New QRZ? default for F9.
With ESM enabled (Settings → CW Keyer), the keyer active and mode CW, Enter in
the entry strip fires a macro by QSO stage instead of logging:
- callsign field empty → F1 (CQ)
- callsign entered (in call field) → F2 (report), then focus jumps to RST-sent
- Enter in RST-sent / RST-rcvd → F3 (TU), which logs via its own <LOGQSO>
Works for every keyer engine (WinKeyer / serial DTR-RTS / Flex CWX / Icom CI-V)
since it routes through the shared macro sender. Enter in other fields still logs
normally, and ESM off keeps the classic Enter-to-log behaviour.
Backend: new `esm` flag on WinkeyerSettings (winkeyer.esm). Frontend: esmHandleEnter
state machine keyed off data-esm markers on the call/RST blocks, a Settings
checkbox + hint, and i18n EN/FR.
Rolling the mouse wheel over the hundreds / tens / units-of-kHz digit of the
header (and compact top-bar) frequency steps the frequency by 100 / 10 / 1 kHz
(wheel up = higher). The display updates optimistically on every notch and the
rig QSYs over CAT, debounced so a fast scroll doesn't flood the link. Only the
kHz digits are wheel-sensitive; MHz and Hz stay static.
New FreqWheelDisplay component (replaces the plain fmtFreqDots span in both the
full header and the compact top bar) + nudgeFreqHz handler.
Addresses three points of feedback on the Tuner Genius work:
- Meter sizes: the amplifier meters were rendered `compact` (smaller than the
FlexRadio meters). Dropped compact so the amp, tuner and Flex meters are all
the same size.
- Collapsible cards: the FlexRadio panel Card, AmpCard and TunerCard now fold
from a chevron in the header, state persisted per card (opslog.cardOpen.*).
The amplifier cards share the "amplifier" collapse key across their SPE/ACOM/
PGXL variants so folding sticks regardless of the shown model.
- TGXL responsiveness: the tuner's device poll dropped 1500ms→400ms and the
three UI pollers 1500ms→500ms, so the SWR/power meters track TX without the
2–3s lag behind the amplifier the user saw.
- Icon: the Tuner Genius top-bar toggle used Zap, same as the CW keyer — changed
the tuner's icon (top bar + widget + card) to Gauge so the two are distinct.
Push the tuner control further so it mirrors the native 4O3A app and the way the
PowerGenius XL is surfaced.
Backend (internal/tunergenius):
- Status now carries both RF channels A and B (source/mode, band, frequency,
bound Flex nickname, per-channel bypass, antenna, PTT), the active channel,
the C1/L/C2 relay-network positions, and the 3-way-vs-SO2R hardware variant
(learned once from the `info` reply). Flat freq/antenna still mirror the active
channel for the compact widget.
- New TunerGeniusActivate(ch) binding → `activate ch=N` (or `ant=N` on 3-way).
Frontend:
- New shared TunerCard, styled exactly like AmpCard (PWR/SWR meter bars,
A/B channel selector with source+freq+antenna, Tune/Bypass/Operate). Used in
BOTH the FlexRadio panel (its own card, like the PGXL) and Station Control.
- Docked TunerGeniusPanel widget now shows the two channels A/B with their
source/frequency/antenna and lets you click to make one active — the missing
A/B state the user flagged.
- i18n EN/FR for the new labels (channels, antenna, in-line/bypassed, title).
Still UNTESTED on hardware — verify the per-channel field names/units and the
activate behaviour on the real box.
New internal/tunergenius client speaking the same "Genius Series" text API
as the Antenna Genius / PowerGenius XL: banner on connect (with optional
"AUTH" for remote access), "C<seq>|<cmd>\n" commands, "R<seq>|<code>|<msg>"
replies and the "S<seq>|status k=v …" snapshot; async "M|<msg>" info lines
are consumed inline. Polls status ~1.5s and exposes SWR (return-loss dB
converted to a VSWR ratio), forward power (dBm→W), and the operate/bypass/
tuning/active-channel state. Actions: autotune, global bypass, operate/standby.
Controlled directly (not via the radio) so OpsLog uses only one of the box's
four connection slots, per the device's protocol doc.
Wiring:
- app.go: tunergenius.* settings keys, Get/Save/start, GetTunerGeniusStatus,
TunerGeniusAutotune/SetBypass/SetOperate; started in the background at launch.
- Settings → Tuner Genius panel (enable + IP + optional remote code).
- Docked TunerGeniusPanel widget (SWR/power readouts + Tune/Bypass/Operate),
top-bar toggle, shown when enabled — mirrors the Antenna Genius widget.
- i18n EN/FR (sec.tunergenius, tg2.*, tgp.*).
Command verbs (operate/bypass/autotune/status/auth) come straight from the
4O3A "Tuner Genius XL — Protocol Description"; UNTESTED on hardware.
award_refs was only reset when the callsign was emptied, so swapping from one
call to another (e.g. clicking successive spots) kept the previous station's
references in the F3 Awards tab. Move the reset to just past the same-call guard
so any change clears them; the new call's spot POTA + live detection re-populate
right after. Changelog 0.21.1.
Add a keyNav prop to BandMap: when set (only the docked Main-view map, not the
multi-band Band Map tab), Ctrl+ArrowUp / Ctrl+ArrowDown selects the next in-band
spot above / below the rig frequency and tunes to it via the existing spot-click
handler. Higher freq is up on the map (freqToY), so Up = next higher spot.
Ignored while typing in an input. Changelog 0.21.1.
Global "Export to ADIF" gains a third option "Choose fields…" next to the
standard-only and full-OpsLog choices, and right-clicking selected QSOs adds
"Export selected — choose fields…". The picker separates official ADIF 3.1.7
fields (grouped by category) from OpsLog/non-standard tags actually present in
the log (discovered via DistinctExtraKeys over extras_json), with All/None per
group and reset-to-defaults; the selection is persisted per user.
Backend: adif.Exporter gains a Fields allow-set that gates every promoted and
extras field in writeRecord; app.go ExportADIF/ExportADIFFiltered/
ExportADIFSelected take a fields []string param, plus new ADIFExtraFields.
The first RX-audio CW decoder decoded poorly on real signals (vs SDC) and was
removed in cafade0. This rebuilds the DSP core from scratch in
internal/cwdecode, keeping v1's good ideas (pitch lock, Flex cw_pitch
targeting, tiered acquisition) and fixing its proven failures:
- Two-way DEBOUNCE (pending-commit ~0.3 dit): v1 rejected short marks but not
short spaces, so a one-hop fade inside a dah shattered it into dits — the
single worst real-signal failure.
- Per-character BATCH dit/dah classification at flush time, using the batch's
own bimodal boundary — the first letter of an over decodes at any speed; the
timing clusters (muDit/muDah) are updated with the same attribution, killing
the classify-then-learn feedback spiral ("all dits at 60 WPM").
- dB-domain envelope with separate floor/peak trackers, hysteresis slicer,
span CAP (30 dB — else quiet backgrounds put the off-threshold in the
analysis-window skirts and letters merge) and window de-bias on durations.
- Double squelch: span >= 6 dB AND peak >= bank-median + 9.5 dB (span alone
cannot reject pure noise).
- Hamming-windowed Goertzel, 16 ms window / 5 ms hop (a 20 ms window left
40 WPM inter-element gaps with no envelope dip).
- Hardened acquisition (overlapping windows made "3 stable hops" meaningless)
plus SUPERVISED RE-LOCK: a clearly stronger tone on another pitch sustained
~1 s takes over — heals noise locks and follows a QSY.
- Gap-fed dit-length tracking (inter-element gaps are timing evidence too).
Proven by a synthetic-signal test suite (all passing): clean 12-40 WPM, three
pitches, added noise, QSB fading 0.35-1.0, 10 ms dropouts inside dahs, QRM in
auto and targeted modes, noise-only squelch, mid-over speed change 25->15 WPM,
and jittered hand keying (+/-20-25%, 3 seeds).
Wiring and UI restored from git (app_cw.go, Ear header button, decoded-text
strip with WPM/pitch/level + pitch lock + click-a-word-to-fill-callsign, Tools
menu entry) — strip strings translated (cwd.* i18n keys, EN/FR) this time.
Adds a 4th CW keyer engine alongside K1EL WinKeyer, Icom CI-V and Flex CWX:
OpsLog bit-bangs the Morse itself on a serial control line (DTR keys CW, RTS
keys PTT — swappable), the hardware-keying method N1MM/WSJT use with a Yaesu
SCU-17 and generic interfaces. No WinKeyer chip needed.
- internal/winkeyer/serialcw.go: serialKeyer — worker goroutine, morse table,
WPM + Farnsworth timing (live speed changes), abortable, PTT lead-in/tail/hang.
- winkeyer.Config gains Type ("k1el"|"serial") + CWKeyLine ("dtr"|"rts");
Manager routes Connect/Send/Stop/SetSpeed/Backspace/Disconnect to it.
- app.go: keyWKCWLine setting; WinkeyerConnect sets Type from the engine.
- Settings -> CW Keyer: new "Serial port (DTR=CW/RTS=PTT)" engine with COM port,
key-line selector, speed/Farnsworth/lead-in/tail/PTT. Macros, auto-CQ and
<LOGQSO> reuse the WinKeyer path unchanged.
UNTESTED on hardware (no SCU-17 to hand): polarity assumes N1MM convention
(line HIGH = active). Weight not yet honoured; Farnsworth is.
- Station Control: the amplifier panel is now the exact same card as the FlexRadio panel (extracted shared AmpCard: OPERATE/STANDBY, ON/OFF, power level, output-power bar, live FWD/ID/temp meters). Every configured amp gets its own card — no dropdown.
- Help → "Send log to F4BPO" (only when SMTP is configured): e-mails opslog.log + previous session + crash log to the developer. New email.SendFiles for multi-attachment.
- Bulk edit: new "Frequency (MHz)" field sets freq_hz AND recomputes band; out-of-band values rejected. Fixes a run logged on a stale/default freq after CAT dropped.
- Cluster: Time column sorted lexically on the HHMMZ string, so spots after 0000Z sorted below 2359Z and looked frozen at the UTC rollover. Now sorts by real arrival time.
- Also folds in the DVK auto-CQ/2-column layout and Station Control dashboard batch (changelog 0.20.10, EN+FR).
Multi-amp: Settings->Amplifier becomes a list (amps.json, legacy single-amp keys auto-migrate to entry #1); one client per enabled amp with per-id bindings (GetAmplifiers/SaveAmplifiers/GetAmpStatuses/AmpOperate/AmpPower/AmpPowerLevel/AmpFanMode); legacy a.pgxl/a.spe/a.acom point at the first enabled amp of each family. Amp cards in FlexPanel and Station Control gain a dropdown to pick the amp; the status bar shows one clickable chip per amp. Use case: two SPEs run in parallel.
Flex v4 DSP (8000/Aurora): NRL/ANFL (lms_nr/lms_anf), NRS (speex_nr), NRF (nrf) with level sliders, RNN (rnnoise) and ANFT on/off — keys per the FlexLib slice docs; the section only shows when the radio reports these keys (dsp_v4 flag), so 6000-series panels are unchanged.
Opening About now fires a fresh CheckForUpdate with a 'Checking…' state, and
clears the update banner when the latest check finds nothing — so it reflects
reality instead of the last periodic check.
Adds GetChangelog (all entries up to the running version, no seen-marker change)
and a Help menu 'What's new' item that reopens the changelog dialog on demand.
Embeds changelog.json (per-version EN + FR notes, curated from the release's
commits). GetWhatsNew compares the stored last-seen version with the running
build and returns the notes for every newer version, once, then advances the
marker. A dialog shows them in the UI language on the first launch after update.
Seeded with the 0.20.5 notes.
publishLiveStatus only ran on QSO log or the 15s heartbeat, and the widget polled
every 15s, so an operator's band/freq (and their appearance) could trail by up to
a minute. Now ReportLiveActivity debounce-publishes ~1.5s after a freq/band/mode
change, and the widget polls every 5s (and 1.5s after qso:logged).
The 15s poll effect depended on the dbConn OBJECT, which gets a fresh reference
on several events — each reset the interval before it fired, so the widget could
stay stale for over a minute. Depend on the is-mysql boolean instead, and also
refresh ~2s after qso:logged so our own row appears promptly like the ON AIR
badge.
- Removes the RX-audio CW decoder: deletes app_cw.go + internal/cwdecode, the
cwDecoder/cwPitchHz/cwMu/cwStop App fields, and all frontend state/effects, the
header Ear button, the Tools menu item and the decoded-text strip. The CW
KEYER (WinKeyer/Icom/Flex) is untouched.
- Fix: typing a callsign while a macro is keying now aborts the CURRENT engine
(Flex CWX / Icom / WinKeyer) on the first character, not just WinKeyer during
an auto-call loop — via a shared stopKeyerTx() helper.
Completes the CWX type-ahead loop: with send-on-type the keyer-panel CW text
already streams each typed char to the radio's CWX buffer (which keys in order,
so you can keep typing while it sends); this adds the matching un-send. Route
wkBackspace to FlexBackspaceCW -> cwx erase N, so backspacing a mistyped char in
send-on-type removes it from the buffer before the radio keys it.
The ESC abort keyed WinkeyerStop unconditionally, so it never aborted the Icom
CI-V or Flex CWX keyer. Route the stop through cwSource like the panel Stop
button does.