[fix](trx-frontend-http): guard CW auto UI against concurrent SSE updates
Add cwAutoLocalOverride flag in cw.js to block server-state snapshots from overriding the checkbox while a user-initiated POST is in-flight. Expose applyCwAutoUiFromServer for app.js render() to call instead of applyCwAutoUi, preventing a racing SSE event carrying the old cw_auto value from immediately undoing the user's toggle. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Signed-off-by: Stan Grams <sjg@haxx.space>
This commit is contained in:
@@ -2479,28 +2479,24 @@ function render(update) {
|
|||||||
const cwAutoEl = document.getElementById("cw-auto");
|
const cwAutoEl = document.getElementById("cw-auto");
|
||||||
const cwWpmEl = document.getElementById("cw-wpm");
|
const cwWpmEl = document.getElementById("cw-wpm");
|
||||||
const cwToneEl = document.getElementById("cw-tone");
|
const cwToneEl = document.getElementById("cw-tone");
|
||||||
if (cwAutoEl && typeof update.cw_auto === "boolean") {
|
|
||||||
cwAutoEl.checked = update.cw_auto;
|
|
||||||
}
|
|
||||||
if (cwWpmEl && typeof update.cw_wpm === "number") {
|
if (cwWpmEl && typeof update.cw_wpm === "number") {
|
||||||
cwWpmEl.value = update.cw_wpm;
|
cwWpmEl.value = update.cw_wpm;
|
||||||
}
|
}
|
||||||
if (cwToneEl && typeof update.cw_tone_hz === "number") {
|
if (cwToneEl && typeof update.cw_tone_hz === "number") {
|
||||||
cwToneEl.value = update.cw_tone_hz;
|
cwToneEl.value = update.cw_tone_hz;
|
||||||
}
|
}
|
||||||
if ((cwWpmEl || cwToneEl) && typeof update.cw_auto === "boolean") {
|
if (typeof update.cw_auto === "boolean") {
|
||||||
const disabled = update.cw_auto;
|
if (typeof window.applyCwAutoUiFromServer === "function") {
|
||||||
if (typeof window.applyCwAutoUi === "function") {
|
// cw.js is loaded: use the guarded path that respects in-flight user
|
||||||
window.applyCwAutoUi(disabled);
|
// changes, preventing a concurrent SSE poll from re-enabling auto just
|
||||||
|
// after the user disabled it.
|
||||||
|
window.applyCwAutoUiFromServer(update.cw_auto);
|
||||||
|
} else if (typeof window.applyCwAutoUi === "function") {
|
||||||
|
window.applyCwAutoUi(update.cw_auto);
|
||||||
} else {
|
} else {
|
||||||
if (cwWpmEl) {
|
if (cwAutoEl) cwAutoEl.checked = update.cw_auto;
|
||||||
cwWpmEl.disabled = disabled;
|
if (cwWpmEl) { cwWpmEl.disabled = update.cw_auto; cwWpmEl.readOnly = update.cw_auto; }
|
||||||
cwWpmEl.readOnly = disabled;
|
if (cwToneEl) { cwToneEl.disabled = update.cw_auto; cwToneEl.readOnly = update.cw_auto; }
|
||||||
}
|
|
||||||
if (cwToneEl) {
|
|
||||||
cwToneEl.disabled = disabled;
|
|
||||||
cwToneEl.readOnly = disabled;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let activeFreqColor = "var(--accent-green)";
|
let activeFreqColor = "var(--accent-green)";
|
||||||
|
|||||||
@@ -21,6 +21,11 @@ let cwLastAppendTime = 0;
|
|||||||
let cwTonePickerRaf = null;
|
let cwTonePickerRaf = null;
|
||||||
let cwPaused = false;
|
let cwPaused = false;
|
||||||
let cwBufferedWhilePaused = 0;
|
let cwBufferedWhilePaused = 0;
|
||||||
|
// Tracks a user-initiated auto toggle that is in-flight (POST not yet
|
||||||
|
// acknowledged). While set, server-state updates must not override the
|
||||||
|
// checkbox so that a concurrent SSE event carrying the *old* cw_auto value
|
||||||
|
// does not immediately undo the user's choice.
|
||||||
|
let cwAutoLocalOverride = null;
|
||||||
|
|
||||||
function applyCwAutoUi(enabled) {
|
function applyCwAutoUi(enabled) {
|
||||||
if (cwAutoInput) cwAutoInput.checked = enabled;
|
if (cwAutoInput) cwAutoInput.checked = enabled;
|
||||||
@@ -38,6 +43,13 @@ function applyCwAutoUi(enabled) {
|
|||||||
}
|
}
|
||||||
window.applyCwAutoUi = applyCwAutoUi;
|
window.applyCwAutoUi = applyCwAutoUi;
|
||||||
|
|
||||||
|
// Called by app.js render() when a server-state snapshot arrives. Ignores
|
||||||
|
// the update while cwAutoLocalOverride is set (user change still in-flight).
|
||||||
|
window.applyCwAutoUiFromServer = function(enabled) {
|
||||||
|
if (cwAutoLocalOverride !== null) return;
|
||||||
|
applyCwAutoUi(enabled);
|
||||||
|
};
|
||||||
|
|
||||||
function clampCwWpm(wpm) {
|
function clampCwWpm(wpm) {
|
||||||
const numeric = Number(wpm);
|
const numeric = Number(wpm);
|
||||||
if (!Number.isFinite(numeric)) return 15;
|
if (!Number.isFinite(numeric)) return 15;
|
||||||
@@ -232,12 +244,15 @@ async function setCwTone(tone, { syncInput = true } = {}) {
|
|||||||
if (cwAutoInput) {
|
if (cwAutoInput) {
|
||||||
cwAutoInput.addEventListener("change", async () => {
|
cwAutoInput.addEventListener("change", async () => {
|
||||||
const enabled = cwAutoInput.checked;
|
const enabled = cwAutoInput.checked;
|
||||||
|
cwAutoLocalOverride = enabled;
|
||||||
applyCwAutoUi(enabled);
|
applyCwAutoUi(enabled);
|
||||||
try {
|
try {
|
||||||
await postPath(`/set_cw_auto?enabled=${enabled ? "true" : "false"}`);
|
await postPath(`/set_cw_auto?enabled=${enabled ? "true" : "false"}`);
|
||||||
drawCwTonePicker();
|
drawCwTonePicker();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error("CW auto toggle failed", e);
|
console.error("CW auto toggle failed", e);
|
||||||
|
} finally {
|
||||||
|
cwAutoLocalOverride = null;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user