The single-worker upload queue introduced head-of-line blocking: one slow
upload (LoTW signing via TQSL, or a service on a 30 s timeout) stalled the
whole queue, so every following QSO's Club Log upload waited and the rows sat
at "R". Operators on the older, concurrent build had no such problem — the
tell that this was a fresh regression.
Back to one goroutine per upload (never serialised), so a slow upload never
holds up the rest, while keeping the transient-failure retry with back-off
(concurrent, so it can't block anything).
A QSO logged via UDP / ADIF-import fired `go m.upload` per QSO, so a pileup or
an ADIF-import burst hit a service with dozens of concurrent requests at once.
Club Log's nginx answers that burst with 403; the upload counts as failed and
the QSO stays at "R" while a lucky few go through — the real cause behind the
"it's uploaded but shows R" report (not the earlier display race).
Route all immediate/delayed auto-uploads through a single serialised worker
(one at a time, 250ms apart) so no burst ever trips a per-IP rate limiter.
upload() now reports whether a failure is transient (HTTP/rate-limit) vs
permanent (not eligible, wrong station callsign, no record); transient ones
are retried with exponential back-off (up to 4 attempts, 1s/2s/4s), so a QSO
that hit a momentary 403 ends up marked instead of stuck at R. A full queue
falls back to a goroutine rather than dropping an upload.