[feat](trx-frontend-http): disable mode-incompatible decoders on bookmark apply

When a bookmark switches to a mode that a currently-enabled toggle
decoder doesn't support (e.g. moving from DIG to FM while FT8 is on),
turn the incompatible decoder off. Previously bmApply only touched
decoders that were compatible with the new mode, leaving stale
decoders running against modulation they can't handle.

Compatible decoders keep their existing behaviour: if the bookmark
specifies a decoder set, toggles are driven to match it; otherwise
they're left as-is.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-04 20:55:50 +02:00
parent ede5e75dca
commit bb45153ede
@@ -449,32 +449,45 @@ async function bmApply(bm) {
} }
})(); })();
// Decoder toggles — fire-and-forget. // Decoder toggles — fire-and-forget.
// Only toggle decoders that are toggle-gated and whose active modes // - Decoders incompatible with the new mode are always turned off
// include the bookmark's mode (driven by the decoder registry). // (even when the bookmark has no explicit decoder selection).
// - For compatible decoders, if the bookmark specifies a set, the
// toggles are driven to match that set; otherwise they're left
// alone.
const hasDecoders = Array.isArray(bm.decoders) && bm.decoders.length > 0; const hasDecoders = Array.isArray(bm.decoders) && bm.decoders.length > 0;
const modeUp = (bm.mode || "").toUpperCase(); const modeUp = (bm.mode || "").toUpperCase();
const toggleDecoders = (window.decoderRegistry || []).filter(d => const allToggleDecoders = (window.decoderRegistry || []).filter(d =>
d.activation === "toggle" && d.active_modes.includes(modeUp) d.activation === "toggle"
); );
const shouldToggle = hasDecoders && toggleDecoders.length > 0; const decoderPromise = allToggleDecoders.length ? (async () => {
const decoderPromise = shouldToggle ? (async () => {
let statusUrl = "/status"; let statusUrl = "/status";
if (typeof lastActiveRigId !== "undefined" && lastActiveRigId) { if (typeof lastActiveRigId !== "undefined" && lastActiveRigId) {
statusUrl += "?remote=" + encodeURIComponent(lastActiveRigId); statusUrl += "?remote=" + encodeURIComponent(lastActiveRigId);
} }
const statusResp = await fetch(statusUrl); const statusResp = await fetch(statusUrl);
if (statusResp.ok) { if (!statusResp.ok) return;
const st = await statusResp.json(); const st = await statusResp.json();
const toggles = []; const toggles = [];
for (const d of toggleDecoders) { for (const d of allToggleDecoders) {
const statusKey = d.id.replace(/-/g, "_") + "_decode_enabled"; const statusKey = d.id.replace(/-/g, "_") + "_decode_enabled";
const wanted = bm.decoders.includes(d.id); const currentlyOn = !!st[statusKey];
if (wanted !== !!st[statusKey]) { const compatible = Array.isArray(d.active_modes)
toggles.push(postPath("/toggle_" + d.id.replace(/-/g, "_") + "_decode")); && d.active_modes.includes(modeUp);
} let wanted;
if (!compatible) {
// Always disable decoders that don't apply to the new mode.
wanted = false;
} else if (hasDecoders) {
wanted = bm.decoders.includes(d.id);
} else {
// Mode-compatible and no bookmark selection: leave as-is.
wanted = currentlyOn;
}
if (wanted !== currentlyOn) {
toggles.push(postPath("/toggle_" + d.id.replace(/-/g, "_") + "_decode"));
} }
if (toggles.length) await Promise.all(toggles);
} }
if (toggles.length) await Promise.all(toggles);
})() : Promise.resolve(); })() : Promise.resolve();
// Don't await — let the network calls settle in the background. // Don't await — let the network calls settle in the background.
// Errors are logged but don't block the UI. // Errors are logged but don't block the UI.