From b96eb8ccbaf9bf4e26ace2c682417fb4bcbea090 Mon Sep 17 00:00:00 2001 From: Smittix Date: Sun, 8 Feb 2026 23:20:02 +0000 Subject: [PATCH] Fix DMR frontend/backend state desync causing 409 on start When the backend has an active DMR session but the frontend lost track (page refresh, broken flags causing silent running), clicking Start returned 409 with no recovery path. Now the frontend resyncs on "Already running" responses and checks backend status on tab activation. Co-Authored-By: Claude Opus 4.6 --- static/js/modes/dmr.js | 49 ++++++++++++++++++++++++++++++++++++++++++ templates/index.html | 1 + 2 files changed, 50 insertions(+) diff --git a/static/js/modes/dmr.js b/static/js/modes/dmr.js index 447afaa..7504dd7 100644 --- a/static/js/modes/dmr.js +++ b/static/js/modes/dmr.js @@ -91,6 +91,21 @@ function startDmr() { if (typeof showNotification === 'function') { showNotification('DMR', `Decoding ${frequency} MHz (${protocol.toUpperCase()})`); } + } else if (data.status === 'error' && data.message === 'Already running') { + // Backend has an active session the frontend lost track of — resync + isDmrRunning = true; + updateDmrUI(); + connectDmrSSE(); + if (!dmrSynthInitialized) initDmrSynthesizer(); + dmrEventType = 'idle'; + dmrActivityTarget = 0.1; + dmrLastEventTime = Date.now(); + updateDmrSynthStatus(); + const statusEl = document.getElementById('dmrStatus'); + if (statusEl) statusEl.textContent = 'DECODING'; + if (typeof showNotification === 'function') { + showNotification('DMR', 'Reconnected to active session'); + } } else { if (typeof showNotification === 'function') { showNotification('Error', data.message || 'Failed to start DMR'); @@ -496,9 +511,43 @@ function stopDmrSynthesizer() { window.addEventListener('resize', resizeDmrSynthesizer); +// ============== STATUS SYNC ============== + +function checkDmrStatus() { + fetch('/dmr/status') + .then(r => r.json()) + .then(data => { + if (data.running && !isDmrRunning) { + // Backend is running but frontend lost track — resync + isDmrRunning = true; + updateDmrUI(); + connectDmrSSE(); + if (!dmrSynthInitialized) initDmrSynthesizer(); + dmrEventType = 'idle'; + dmrActivityTarget = 0.1; + dmrLastEventTime = Date.now(); + updateDmrSynthStatus(); + const statusEl = document.getElementById('dmrStatus'); + if (statusEl) statusEl.textContent = 'DECODING'; + } else if (!data.running && isDmrRunning) { + // Backend stopped but frontend didn't know + isDmrRunning = false; + if (dmrEventSource) { dmrEventSource.close(); dmrEventSource = null; } + updateDmrUI(); + dmrEventType = 'stopped'; + dmrActivityTarget = 0; + updateDmrSynthStatus(); + const statusEl = document.getElementById('dmrStatus'); + if (statusEl) statusEl.textContent = 'STOPPED'; + } + }) + .catch(() => {}); +} + // ============== EXPORTS ============== window.startDmr = startDmr; window.stopDmr = stopDmr; window.checkDmrTools = checkDmrTools; +window.checkDmrStatus = checkDmrStatus; window.initDmrSynthesizer = initDmrSynthesizer; diff --git a/templates/index.html b/templates/index.html index b40c8e3..5c90827 100644 --- a/templates/index.html +++ b/templates/index.html @@ -3207,6 +3207,7 @@ SSTVGeneral.init(); } else if (mode === 'dmr') { if (typeof checkDmrTools === 'function') checkDmrTools(); + if (typeof checkDmrStatus === 'function') checkDmrStatus(); if (typeof initDmrSynthesizer === 'function') setTimeout(initDmrSynthesizer, 100); } else if (mode === 'websdr') { if (typeof initWebSDR === 'function') initWebSDR();