diff --git a/src/trx-client/trx-frontend/trx-frontend-http/assets/web/plugins/scheduler.js b/src/trx-client/trx-frontend/trx-frontend-http/assets/web/plugins/scheduler.js
index 44af2e6..e586f32 100644
--- a/src/trx-client/trx-frontend/trx-frontend-http/assets/web/plugins/scheduler.js
+++ b/src/trx-client/trx-frontend/trx-frontend-http/assets/web/plugins/scheduler.js
@@ -206,6 +206,11 @@
if (active.length === 1) {
return { activeEntries: active, currentIndex: 0, remainingSec: 0, cycleMin: 0 };
}
+ // Exclusive entry wins outright — no interleaving.
+ var exclIdx = active.findIndex(function (e) { return e.exclusive; });
+ if (exclIdx >= 0) {
+ return { activeEntries: [active[exclIdx]], currentIndex: 0, remainingSec: 0, cycleMin: 0 };
+ }
const defaultInterleave = Number(config.interleave_min);
const durations = active.map(function (entry) {
const own = Number(entry && entry.interleave_min);
@@ -820,7 +825,8 @@
'' +
'' +
'
| ' +
- ' | ' +
+ '' +
+ ' | ' +
' | ' +
' | ';
@@ -862,6 +868,16 @@
extraPick.value = '';
});
+ // Wire exclusive checkbox to disable interleave input.
+ var exclEl = tr.querySelector('[data-field="exclusive"]');
+ var ilInput = tr.querySelector('[data-field="interleave"]');
+ if (exclEl && ilInput) {
+ exclEl.addEventListener('change', function () {
+ ilInput.disabled = exclEl.checked;
+ if (exclEl.checked) ilInput.value = '';
+ });
+ }
+
tr.querySelector('.sch-inline-save').addEventListener('click', function () {
var startEl = tr.querySelector('[data-field="start"]');
var endEl = tr.querySelector('[data-field="end"]');
@@ -869,6 +885,7 @@
var labelEl = tr.querySelector('[data-field="label"]');
var ilEl = tr.querySelector('[data-field="interleave"]');
var recEl = tr.querySelector('[data-field="record"]');
+ var exEl = tr.querySelector('[data-field="exclusive"]');
if (bmEl && !bmEl.value) { alert('Please select a bookmark.'); return; }
@@ -876,8 +893,9 @@
entry.end_min = hhmmToMin(endEl.value);
entry.bookmark_id = bmEl.value;
entry.label = labelEl.value.trim() || null;
+ entry.exclusive = exEl ? exEl.checked : false;
var ilVal = parseInt(ilEl.value, 10);
- entry.interleave_min = (!isNaN(ilVal) && ilVal > 0) ? ilVal : null;
+ entry.interleave_min = entry.exclusive ? null : ((!isNaN(ilVal) && ilVal > 0) ? ilVal : null);
entry.bookmark_ids = inlineExtraIds.slice();
entry.record = recEl.checked;
@@ -908,7 +926,7 @@
entry.id && String(entry.id) === String(currentSchedulerStatus.last_entry_id)) {
tr.classList.add("sch-active");
}
- const il = entry.interleave_min ? String(entry.interleave_min) + " min" : "—";
+ const il = entry.exclusive ? "Exclusive" : entry.interleave_min ? String(entry.interleave_min) + " min" : "—";
const allDay = entry.start_min === entry.end_min;
const centerCell = entry.center_hz ? formatFreq(entry.center_hz) : "—";
const extraIds = Array.isArray(entry.bookmark_ids) ? entry.bookmark_ids : [];
diff --git a/src/trx-client/trx-frontend/trx-frontend-http/src/scheduler.rs b/src/trx-client/trx-frontend/trx-frontend-http/src/scheduler.rs
index 237f861..c7a8941 100644
--- a/src/trx-client/trx-frontend/trx-frontend-http/src/scheduler.rs
+++ b/src/trx-client/trx-frontend/trx-frontend-http/src/scheduler.rs
@@ -89,6 +89,12 @@ pub struct ScheduleEntry {
/// Whether to auto-record audio when this entry is active.
#[serde(default)]
pub record: bool,
+ /// When `true`, this entry is never interleaved with other overlapping
+ /// entries. While this entry's time window is active the scheduler stays
+ /// on its bookmark until the window ends. Useful for WEFAX and satellite
+ /// passes where switching away mid-reception would lose data.
+ #[serde(default)]
+ pub exclusive: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
@@ -490,6 +496,12 @@ fn timespan_active_entry(
return None;
}
+ // If any active entry is exclusive, it wins outright (first exclusive
+ // entry in schedule order takes priority).
+ if let Some(excl) = active.iter().find(|e| e.exclusive) {
+ return Some(excl);
+ }
+
if let Some(idx) = timespan_cycle_slot(&active, now_min, default_interleave) {
return Some(active[idx]);
}
@@ -1500,6 +1512,7 @@ mod tests {
center_hz,
bookmark_ids: Vec::new(),
record: false,
+ exclusive: false,
}
}
@@ -1553,4 +1566,20 @@ mod tests {
let active = timespan_active_entry(&entries, 10.0, None).expect("active entry");
assert_eq!(active.id, "slot-b");
}
+
+ #[test]
+ fn exclusive_entry_wins_over_interleaved() {
+ let mut entries = vec![
+ entry("ft8", 0, 0, "bm-ft8", None, Some(10)),
+ entry("wefax", 0, 0, "bm-wefax", None, Some(10)),
+ ];
+ // Without exclusive, interleaving picks slot-b at minute 15.
+ let active = timespan_active_entry(&entries, 15.0, None).expect("active entry");
+ assert_eq!(active.id, "wefax");
+
+ // Mark wefax as exclusive — it should always win regardless of cycle.
+ entries[1].exclusive = true;
+ let active = timespan_active_entry(&entries, 5.0, None).expect("active entry");
+ assert_eq!(active.id, "wefax", "exclusive entry should win at any time");
+ }
}