diff --git a/src/trx-client/trx-frontend/trx-frontend-http/assets/web/app.js b/src/trx-client/trx-frontend/trx-frontend-http/assets/web/app.js index 577f59b..d8c0d7e 100644 --- a/src/trx-client/trx-frontend/trx-frontend-http/assets/web/app.js +++ b/src/trx-client/trx-frontend/trx-frontend-http/assets/web/app.js @@ -2479,28 +2479,24 @@ function render(update) { const cwAutoEl = document.getElementById("cw-auto"); const cwWpmEl = document.getElementById("cw-wpm"); 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") { cwWpmEl.value = update.cw_wpm; } if (cwToneEl && typeof update.cw_tone_hz === "number") { cwToneEl.value = update.cw_tone_hz; } - if ((cwWpmEl || cwToneEl) && typeof update.cw_auto === "boolean") { - const disabled = update.cw_auto; - if (typeof window.applyCwAutoUi === "function") { - window.applyCwAutoUi(disabled); + if (typeof update.cw_auto === "boolean") { + if (typeof window.applyCwAutoUiFromServer === "function") { + // cw.js is loaded: use the guarded path that respects in-flight user + // 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 { - if (cwWpmEl) { - cwWpmEl.disabled = disabled; - cwWpmEl.readOnly = disabled; - } - if (cwToneEl) { - cwToneEl.disabled = disabled; - cwToneEl.readOnly = disabled; - } + if (cwAutoEl) cwAutoEl.checked = update.cw_auto; + if (cwWpmEl) { cwWpmEl.disabled = update.cw_auto; cwWpmEl.readOnly = update.cw_auto; } + if (cwToneEl) { cwToneEl.disabled = update.cw_auto; cwToneEl.readOnly = update.cw_auto; } } } let activeFreqColor = "var(--accent-green)"; diff --git a/src/trx-client/trx-frontend/trx-frontend-http/assets/web/plugins/cw.js b/src/trx-client/trx-frontend/trx-frontend-http/assets/web/plugins/cw.js index 7dd55bc..9829859 100644 --- a/src/trx-client/trx-frontend/trx-frontend-http/assets/web/plugins/cw.js +++ b/src/trx-client/trx-frontend/trx-frontend-http/assets/web/plugins/cw.js @@ -21,6 +21,11 @@ let cwLastAppendTime = 0; let cwTonePickerRaf = null; let cwPaused = false; 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) { if (cwAutoInput) cwAutoInput.checked = enabled; @@ -38,6 +43,13 @@ function applyCwAutoUi(enabled) { } 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) { const numeric = Number(wpm); if (!Number.isFinite(numeric)) return 15; @@ -232,12 +244,15 @@ async function setCwTone(tone, { syncInput = true } = {}) { if (cwAutoInput) { cwAutoInput.addEventListener("change", async () => { const enabled = cwAutoInput.checked; + cwAutoLocalOverride = enabled; applyCwAutoUi(enabled); try { await postPath(`/set_cw_auto?enabled=${enabled ? "true" : "false"}`); drawCwTonePicker(); } catch (e) { console.error("CW auto toggle failed", e); + } finally { + cwAutoLocalOverride = null; } }); }