The PH/CW/DIG grid in the Stats panel is the fastest read in the app and its
palette was fixed per theme. Settings -> Appearance now offers the six: the
four status fills, the never-worked fill, and the ring on the cell being
entered.
Stored as OVERRIDES, not as a palette. Each of the twelve themes ships an
--mx-* ramp tuned to its own background, so an operator who only wants a
different green must not thereby freeze the other four to the theme they
happened to be using that day. An empty value means "whatever the theme
says"; the chosen ones are stamped inline on <html>, where they win over
every theme; switching the feature off hands the colours straight back.
The pickers are seeded from what the matrix is painting at that moment
rather than from a fixed palette, so the choice starts from the colours in
front of the operator. A new --mx-cur token carries the current-entry ring:
it follows --warning by default, so it stays theme-correct on all twelve,
but can be recoloured without dragging every other warning in the app along.
The legend under the matrix and its cell tooltips were hardcoded English.
They now go through t() with the same keys as the pickers, so the grid and
the settings cannot disagree about which green is which.
An operator's screenshot had it side by side: the Awards panel showed Morocco
validated on five bands, and the band/mode matrix two inches above showed the
entity as merely worked.
ADIF's QSL_Rcvd enumeration has both Y and V — "received" and "verified" — and
V is what a LoTW download writes for a confirmation the ARRL has validated. The
award engine's isYes accepted "Y" or "V". Everything else in the app compared
against 'Y' alone: the worked-before status grid, the slot statistics, the row
colouring by QSL status, the awards QSO list, the call history badges. So the
confirmations an operator cares most about were the ones that did not count.
Now one definition per side, named so the next reader finds the other:
qso.ConfirmedValues on the Go side, used by the queries themselves, and
isQSLConfirmed in lib/qsl on the frontend, which rowColors and the panels call
instead of testing the letter.
The Go test drives the real query shape against a real database with a 'V' row
— the constant being right is not the point, the queries using it is.
The dialog walked a hand-written GROUPS array. "The contact" was added to the
fields and not to it, so mode, submode and RST existed, passed every check I had
written, and could not be picked — the same shape of fault as the missing column
mapping, one layer further out. Two lists that must agree, with nothing making
them agree.
GROUPS is derived from the fields now, in declaration order, so adding a group
is adding a field. A group with no translation falls back to its own name rather
than rendering an empty heading: a missing label should look untidy, not
invisible.
Also stacks the DX sunrise/sunset in the band-slot header. Side by side it cost
about 150 px of a row that also carries the callsign, the badges and the band
grid, and it was what pushed that row onto a second line. Two short times one
above the other cost a fraction of it and no extra height — the row is already
taller than one line of text. The "UTC" label goes to the tooltip: the times are
monospaced and always UTC everywhere in OpsLog, so it was spending width to
repeat a convention the operator already lives by.
The pop-up listing the QSOs behind a Stats band/mode square gets a cleaner header
(band chip, hint), banded/hover rows and roomier spacing. Callsigns are now
clickable and open the QSO editor (App.openEdit threaded through DetailsPanel →
BandSlotGrid → SlotQSOModal as onEditQso), matching the double-click-to-edit of
the log grids.
Five operator-requested items:
- Alt+W clears the QSO entry. Handled before the `typing` guard and above
the keyer's key routing, so there is always one key that clears whatever
else is running — Esc is not that key when the CW keyer reserves it.
- The Grid box no longer pops outside the entry panel on a narrow window.
Row 2 needed 300+130+76+gaps = 538 px inside a panel whose min-width is
520, so Grid was pushed out and clipped at every narrow width, not just
extreme ones. QTH's min-width drops to 80 and the row wraps rather than
overflowing if it ever still can't fit.
- Selecting a QSO in the log now drives the Stats (F1) matrix. Uses its own
WorkedBefore call into separate state, NOT runWorkedBefore: that one owns
the entry form's wbRef and can trigger a field backfill, which browsing
the log must never do. The entry form wins whenever it holds a call.
- Clicking a coloured band/mode square lists the contacts behind it. Returns
the exact callsign AND the rest of the entity, because that pair is what
the cell's colour encodes; the call's own QSOs are bolded. The DXCC arm
matches the stored dxcc column only — reconstructing it from the callsign
here would disagree with the matrix above, which is built from that column.
- The Awards DXCC list shows each entity's primary prefix in its own sortable
column. Derived live from cty.dat into Ref.Group rather than stored on the
reference row, so an existing installation needs no re-seed.
Opt-in (Settings → General). Shows a "MW #rank" pill next to the DXCC entity
name in the entry-strip band/slot matrix — ClubLog's Most Wanted ranking
(1 = most wanted), personalised to the operator's callsign and refreshed daily.
- internal/clublog/mostwanted.go: fetch mostwanted.php?api=1&callsign=<CALL>
(rank→DXCC JSON, real User-Agent), invert to dxcc→rank, cache to
<dataDir>/clublog_mostwanted.json; NeedsRefresh invalidates on callsign change.
- app.go: keyClublogMostWanted setting, a.clublogMW, startup refresh goroutine,
activeCallsign() helper, and Get/Set/Download bindings mirroring the cty ones.
WorkedBefore now carries MWRank (qso.WorkedBefore.MWRank) when enabled.
- BandSlotGrid.tsx: MW pill next to the entity name, colour-tiered by rank.
- SettingsModal General: toggle + download button + status, mirroring the
ClubLog cty-exceptions block.
Also reorganises the changelog: the theme-persistence fix (landed after the
v0.20.11 tag) plus this feature go under a new 0.20.12 entry; 0.20.11 keeps only
what shipped in its binary.
Backend (Go 1.25 / Wails v2):
- QSO storage on SQLite (modernc) with embedded migrations (0001..0005)
- Streaming ADIF import (batch insert) + WorkedBefore per callsign and DXCC
- Callsign lookup with QRZ.com + HamQTH providers (primary/failsafe routing)
and SQLite-backed TTL cache
- DXCC resolver from cty.dat (auto-download, longest-prefix-match)
- Multi-profile operator identities (home/portable/SOTA/contest) — every
QSO stamps MY_* from the active profile
- CAT control via OmniRig COM on a single OS-locked goroutine, with
bidirectional sync (freq/mode/band/split/VFOs) and Rig1/Rig2 hot-swap
- Settings store (key/value), CAT debug log at %APPDATA%/HamLog/cat.log
Frontend (React 18 + TypeScript + Tailwind v4 + shadcn-style):
- Single-row entry strip with CAT-aware band/mode/freq, RST, Start/End
UTC, per-field locks (band/mode/freq/start/end) for backdated QSOs
- Topbar: live freq (MHz.kHz.Hz dotted), live UTC, band/mode/SPLIT badges,
CAT pill with rig selector and clickable Azimuth pill (rotor TODO)
- Settings tree: Profiles (Log4OM-style manager), Station Information
(edits the active profile), unified Callsign Lookup with Test buttons,
Bands/Modes lists, CAT
- Worked-before matrix (band × mode × class) with new-DXCC highlighting
- ADIF import from menu + Maintenance > Refresh cty.dat
Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>