tune-links drove the dial with Playwright's fill(), which writes a value
into the field without a keystroke. The app arms its guard against its own
refreshes on the first keydown, so a filled field stays unguarded: any state
update landing between the fill and the Enter rewrites the field with the
frequency the radio is already on, and the Enter then re-applies that. The
window is a few milliseconds wide on a developer's machine and wide enough
to lose on a loaded CI runner, where the test failed claiming the tuning had
landed on the frequency it started from.
Type it the way an operator does: select the field, then send the characters
as keystrokes. The select arms the guard before a single character changes.
Under CPU throttling that reproduced the failure — 1 in 6 runs with fill(),
on this branch and on main alike — typing came through 8 runs clean.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SyX26FCpMQxiBoC7r5K1A7
Signed-off-by: Stan Grams <sjg@haxx.space>
A receiver spreads by being linked to, and there was nothing to link to:
the routes carried the tab and nothing else, so "listen to this" could
only ever mean a screenshot and a frequency typed out in a message.
The query string now carries the dial -- rig, frequency, mode and
bandwidth -- in both directions. Opening a link selects the rig, sets
the mode, tunes, then applies the bandwidth: a mode change brings its
own default bandwidth with it, so an explicit bw has to land after it.
Frequencies are read the way someone writes them by hand (7074k,
14.074M) and written back as whole Hz, so what comes out of the address
bar is the same link in canonical form.
After that the address bar keeps up with the dial, which is what makes
it copyable at any moment rather than only at load. It is rewritten
with replaceState -- tuning is not navigation, and a swept dial would
otherwise bury the back button. A link button in the top bar copies
the current link; it folds into the overflow menu when the bar is tight.
Applying a link changes the radio, so an rx session says so instead of
failing control calls one at a time. A tab listening to a virtual
channel leaves the address alone rather than publishing a frequency the
rig is not on, and bw is skipped in both directions on rigs without
filter control, which would only refuse it.
The fixture pinned every state frame to 100 MHz plus jitter to keep
frames distinct, so no test could observe tuning at all. The jitter
moves to the S-meter and the fixture echoes set_freq/set_mode/
set_bandwidth, as it already did for squelch.
Signed-off-by: Stan Grams <sjg@haxx.space>