The filter keeps a REFERENCE for its unconfirmed slots, so its confirmed ones
come along — and green on grey means the answer is the quietest thing in the
row. Faded to a quarter under that filter only, not hidden: which band is
already done is part of deciding which one to chase.
The Challenge line says 1832 worked against 1554 confirmed and stops there —
278 band-slots waiting for a card with nothing pointing at them. Every existing
filter works per REFERENCE, so an entity confirmed on 20 m answers 'confirmed'
and its unconfirmed 15 m contact stays invisible.
The new filter keeps the references holding at least one such slot, and the
count beside the reference total is the gap itself, recomputed over whatever the
other filters left on screen and over the bands actually displayed — so it
always adds up to the columns in front of the operator.
Two things from one screenshot, and the second is arithmetic rather than a
bug.
Editing an award refreshed the grid and not the statistics matrix, so
changing which confirmations count left the table showing the old rule's
numbers with nothing to mark them stale. That is how a setting comes to
look as though it does nothing.
And OpsLog's 3229 against LoTW's 3004: the Challenge counts confirmed
band-slots on ten bands, and 60 m is not one of them. 3229 less the 224 in
the 60 m column is 3005. So the number was right and the QUESTION was
different — the panel now answers the Challenge one too, under the table
where the comparison is made.
The two totals also say what they are on hover. 'Total' and 'Général' side
by side tell nobody which counts entities and which counts band-slots, and
3309 read as entities is an impossible number — which is exactly how it
was read.
The columns took the award's declared band list when it had one, so every
band the award PERMITS got a column whether or not anything had ever been
worked on it. DDFM permits 6m and 70cm; a station that has never worked a
French department on either was shown two empty columns, which reads as a
gap in the log rather than as a band nobody tried.
They now follow the bands this operator actually has contacts on for the
award. The declared list is the fallback for an award with nothing worked
yet — there, saying what the award is played on is the only honest answer
available — and the classic HF set is the fallback for an award that
declares nothing either.
Together with taking the order from the full band list, this is what puts
23cm on DDFM: the column appears because there are contacts on it.
The columns were the intersection of the award's bands with a fixed list
that ended at 70cm, so a band the award genuinely covers could not appear
whatever it said. DDFM is worked well into the microwaves and had no 23cm
column, and nothing anywhere would have produced one.
The intersection is now taken against every band in frequency order, the
same list internal/award already sorts its own band counts by. The classic
HF-plus-2m-and-70cm set stays as the fallback for an award with no band
list and no contacts yet — the full list would be twenty-nine columns of
mostly nothing.
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.
"In this award's scope but with no reference" needs a scope to be in. On a
worldwide reference award — POTA, SOTA, IOTA, WWFF — every contact anywhere
would qualify, so the backend returns nothing and the window says it found
nothing. The button was there regardless, and could only ever open that.
It now appears only for an award scoped to a DXCC entity, which is what the
help text underneath used to have to explain. Better than what I did first,
which was to make a useless screen fast.
Kept from that: the assign dropdown is searched rather than scrolled above 300
references, and the reference list is not fetched until there are rows to
assign it to. Both still bite on a scoped award with a long list — Russian
districts, the bigger European ones.
Reported as POTA: missing references, and it hangs.
The Missing-refs modal builds a dropdown of every reference the award has, one
menu item each. A POTA log with the park list imported holds tens of thousands
of them, and putting that many items in the DOM stops the window answering.
Russian districts and the bigger European lists are the same shape.
Two things, and the second is the sharper one.
The list is now searched rather than scrolled: above 300 references a filter box
appears beside the dropdown, and the menu renders at most that many, saying how
many it is holding back. Nobody scrolls to K-4521 — they type it.
And the modal no longer fetches the references at all until there is something
to assign them to. Missing-reference detection needs a DXCC scope, so a
worldwide award like POTA always has zero rows here and the modal says so — it
was loading every park behind that message, for a dropdown that could not be
used for anything. The freeze happened on a screen with nothing to offer.
Whether this is the freeze that was reported I cannot say from here; it is a
freeze on exactly that screen, for exactly that award.
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.
New per-profile "tracked awards" selection: Settings → Awards (under User
configuration) is a two-column transfer list — every defined award on the left,
the ones you follow on the right, click to move either way. The Awards tab's
list is narrowed to the followed set; an empty set means "show them all" so the
tab is never blank.
Backend: app_awards_tracked.go adds keyAwardsTracked (per-profile JSON array of
award codes) with GetTrackedAwards/SaveTrackedAwards; saving emits
awards:tracked-changed so the Awards tab re-filters live. Award definitions stay
global — only the follow selection is per profile.
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.
Selecting CW showed FT8 and SSB contacts. The filter ran client-side over
the reference list and nothing else, so a reference kept because it had
one CW contact still displayed the band cells it earned on SSB, still
counted its SSB confirmations, and opening it listed every contact
regardless of mode.
The class now narrows the log BEFORE the engine runs, so the bands, the
totals and the confirmations all describe the selected mode, and
AwardCellQSOs takes the same class. The panel cache is keyed by
"CODE|MODECLASS" — one key per award showed the previous mode's numbers
after switching.
The snapshot is cached by log revision, so this costs a matching pass and
no database read.
All / CW / Phone / Digital, stacked on top of the worked/confirmed filter. The
question an operator actually has — which DXCC entities have I worked on CW but
not confirmed on CW — needs both at once; answering only one of them is what
sends people to a spreadsheet.
References carried bands but not modes, so the award computation now aggregates
mode CLASSES per reference, worked and confirmed separately. Classes, not ADIF
modes: nobody chasing CW cares whether the digital side was FT8 or RTTY, and a
list of twenty mode names would not answer the question. An unrecognised mode
counts as data rather than as nothing, so a new digital mode does not vanish
from the filter the day it appears.
Two rules the filter needs to be honest:
- Not-confirmed means not confirmed ON THIS MODE. An entity worked on CW and
confirmed on SSB is still a CW entity to chase, and that is exactly what the
filter is for.
- "Not worked" plus a mode is a contradiction — an unworked reference has no
mode — so the mode filter stands aside there instead of emptying the list.
The sort choice is now written through writeUiPref and both keys are registered
as portable, so they travel with a copied data/ folder like every other UI
preference.
Registering them is the part that matters: writeUiPref stores to the DB, but
only keys on the portable list are read BACK at startup. A key written and never
restored looks exactly like one that was never saved — which is what would have
happened here.
The grey-line toggle had the same gap. It called writeUiPref from the day it was
written but was never added to the list, so it came back off on the next launch.
Found while adding the award keys beside it.
Reference stays the default: that is how award lists are published and how a
paper list is read. Description answers the other question an operator actually
has — "have I worked anything in Alicante" — which reference order scatters
across eight thousand rows.
Sorted where the filtered list is built, so the grid and list views inherit it
rather than each growing its own copy that could disagree.
localeCompare rather than a byte comparison, because these lists are Spanish,
French and Italian place names: "Ávila" belongs beside "Avilés", not after
"Zaragoza". References compare numerically for the same reason in reverse —
08019 must not sort between 0801 and 081.