[fix](trx-frontend-http): prevent SSE from snapping freq back during optimistic update
When the user changes frequency, applyLocalTunedFrequency sets lastFreqHz optimistically. But the SSE state stream could push back the old server frequency before set_freq completes, causing the marker to snap back then forward. Add a sequence-guarded _freqOptimisticHz that suppresses stale SSE frequency updates while a set_freq is in flight. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> Signed-off-by: Stan Grams <sjg@haxx.space>
This commit is contained in:
@@ -1938,6 +1938,11 @@ async function ensureTunedBandwidthCoverage(freqHz, bandwidthHz = coverageGuardB
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Guard: while a set_freq is in flight, SSE state updates must not overwrite
|
||||||
|
// the optimistic local frequency with the stale server value.
|
||||||
|
let _freqOptimisticHz = null;
|
||||||
|
let _freqOptimisticSeq = 0;
|
||||||
|
|
||||||
async function setRigFrequency(freqHz) {
|
async function setRigFrequency(freqHz) {
|
||||||
const targetHz = Math.round(freqHz);
|
const targetHz = Math.round(freqHz);
|
||||||
if (!freqAllowed(targetHz)) {
|
if (!freqAllowed(targetHz)) {
|
||||||
@@ -1946,6 +1951,8 @@ async function setRigFrequency(freqHz) {
|
|||||||
}
|
}
|
||||||
// Optimistic local update before any network round-trip.
|
// Optimistic local update before any network round-trip.
|
||||||
const prevFreqHz = lastFreqHz;
|
const prevFreqHz = lastFreqHz;
|
||||||
|
const seq = ++_freqOptimisticSeq;
|
||||||
|
_freqOptimisticHz = targetHz;
|
||||||
applyLocalTunedFrequency(targetHz);
|
applyLocalTunedFrequency(targetHz);
|
||||||
try {
|
try {
|
||||||
// set_freq and set_center_freq are independent server operations — run in parallel.
|
// set_freq and set_center_freq are independent server operations — run in parallel.
|
||||||
@@ -1958,6 +1965,9 @@ async function setRigFrequency(freqHz) {
|
|||||||
// actual server-side frequency (e.g. when the user loses control access).
|
// actual server-side frequency (e.g. when the user loses control access).
|
||||||
if (prevFreqHz != null) applyLocalTunedFrequency(prevFreqHz, true);
|
if (prevFreqHz != null) applyLocalTunedFrequency(prevFreqHz, true);
|
||||||
throw err;
|
throw err;
|
||||||
|
} finally {
|
||||||
|
// Only clear the guard if no newer optimistic call has superseded us.
|
||||||
|
if (_freqOptimisticSeq === seq) _freqOptimisticHz = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2919,7 +2929,17 @@ function render(update) {
|
|||||||
updateSdrGainInputState();
|
updateSdrGainInputState();
|
||||||
}
|
}
|
||||||
if (update.status && update.status.freq && typeof update.status.freq.hz === "number") {
|
if (update.status && update.status.freq && typeof update.status.freq.hz === "number") {
|
||||||
applyLocalTunedFrequency(update.status.freq.hz, true);
|
const sseHz = update.status.freq.hz;
|
||||||
|
// While an optimistic set_freq is in flight, suppress SSE updates that
|
||||||
|
// would snap the marker back to the stale server frequency.
|
||||||
|
if (_freqOptimisticHz != null && Math.abs(sseHz - _freqOptimisticHz) > 1) {
|
||||||
|
// stale — skip
|
||||||
|
} else {
|
||||||
|
if (_freqOptimisticHz != null && Math.abs(sseHz - _freqOptimisticHz) <= 1) {
|
||||||
|
_freqOptimisticHz = null; // server confirmed — clear guard early
|
||||||
|
}
|
||||||
|
applyLocalTunedFrequency(sseHz, true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (update.status && update.status.mode) {
|
if (update.status && update.status.mode) {
|
||||||
const mode = normalizeMode(update.status.mode);
|
const mode = normalizeMode(update.status.mode);
|
||||||
@@ -3608,6 +3628,9 @@ async function jogFreq(direction) {
|
|||||||
jogIndicator.style.transform = `translateX(-50%) rotate(${jogAngle}deg)`;
|
jogIndicator.style.transform = `translateX(-50%) rotate(${jogAngle}deg)`;
|
||||||
showHint("Setting frequency…");
|
showHint("Setting frequency…");
|
||||||
// Optimistic local state update — visible to the user before the network call.
|
// Optimistic local state update — visible to the user before the network call.
|
||||||
|
// Set the guard BEFORE yielding so SSE cannot snap back during the yield.
|
||||||
|
++_freqOptimisticSeq;
|
||||||
|
_freqOptimisticHz = newHz;
|
||||||
applyLocalTunedFrequency(newHz);
|
applyLocalTunedFrequency(newHz);
|
||||||
// Yield so the browser paints the updated freq display before the network
|
// Yield so the browser paints the updated freq display before the network
|
||||||
// round-trip begins. Chrome's INP tracking ends at the yield point.
|
// round-trip begins. Chrome's INP tracking ends at the yield point.
|
||||||
|
|||||||
@@ -338,6 +338,11 @@ async function bmApply(bm) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (typeof applyLocalTunedFrequency === "function") {
|
if (typeof applyLocalTunedFrequency === "function") {
|
||||||
|
// Set optimistic guard before applying so SSE cannot snap back.
|
||||||
|
if (typeof _freqOptimisticSeq !== "undefined") {
|
||||||
|
++_freqOptimisticSeq;
|
||||||
|
_freqOptimisticHz = bm.freq_hz;
|
||||||
|
}
|
||||||
applyLocalTunedFrequency(bm.freq_hz);
|
applyLocalTunedFrequency(bm.freq_hz);
|
||||||
}
|
}
|
||||||
if (typeof scheduleSpectrumDraw === "function" && typeof lastSpectrumData !== "undefined" && lastSpectrumData) {
|
if (typeof scheduleSpectrumDraw === "function" && typeof lastSpectrumData !== "undefined" && lastSpectrumData) {
|
||||||
|
|||||||
Reference in New Issue
Block a user