[fix](trx-frontend-http): reduce INP on freq input from ~1500ms to <50ms

Chrome classifies wheel events as "pointer" interactions and tracks async
continuations initiated by the handler. The wheel-on-freq-input path was:
  jogFreq → setRigFrequency → postPath(/set_freq) [~700ms]
                             → ensureTunedBandwidthCoverage [~700ms]
...two sequential network round-trips totalling ~1400ms of INP.

Three changes:

1. yieldToMain(): add a scheduler.yield() / setTimeout(0) helper that
   yields the main thread back to the browser.  Chrome's INP interaction
   tracking ends at the yield point, so the network RTT no longer counts.

2. jogFreq: call applyLocalTunedFrequency() optimistically before the
   yield so the freq display updates are visible in the very next paint,
   then yield before firing any network requests.

3. setRigFrequency: move applyLocalTunedFrequency() before the awaits
   (consistent optimistic-update contract for all callers), and run
   postPath(/set_freq) and ensureTunedBandwidthCoverage() in parallel
   via Promise.all — they are independent server operations.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
This commit is contained in:
2026-03-09 23:10:18 +01:00
parent 003075c5e4
commit e29b7ed3d3
@@ -1457,9 +1457,13 @@ async function setRigFrequency(freqHz) {
showUnsupportedFreqPopup(targetHz);
throw new Error(`Unsupported frequency: ${targetHz}`);
}
await postPath(`/set_freq?hz=${targetHz}`);
// Optimistic local update before any network round-trip.
applyLocalTunedFrequency(targetHz);
await ensureTunedBandwidthCoverage(targetHz);
// set_freq and set_center_freq are independent server operations — run in parallel.
await Promise.all([
postPath(`/set_freq?hz=${targetHz}`),
ensureTunedBandwidthCoverage(targetHz),
]);
}
function spectrumBinIndexForHz(data, hz) {
@@ -2750,6 +2754,15 @@ function disconnect() {
}
}
// Yield the main thread so the browser can paint before heavy async work.
// Uses scheduler.yield() (Chrome 115+) with a setTimeout fallback.
function yieldToMain() {
if (typeof scheduler !== "undefined" && typeof scheduler.yield === "function") {
return scheduler.yield();
}
return new Promise((resolve) => setTimeout(resolve, 0));
}
async function postPath(path) {
const resp = await fetch(path, { method: "POST" });
if (authEnabled && resp.status === 401) {
@@ -2949,6 +2962,11 @@ async function jogFreq(direction) {
jogAngle = (jogAngle + direction * 15) % 360;
jogIndicator.style.transform = `translateX(-50%) rotate(${jogAngle}deg)`;
showHint("Setting frequency…");
// Optimistic local state update — visible to the user before the network call.
applyLocalTunedFrequency(newHz);
// Yield so the browser paints the updated freq display before the network
// round-trip begins. Chrome's INP tracking ends at the yield point.
await yieldToMain();
try {
await setRigFrequency(newHz);
showHint("Freq set", 1000);