From dca01ead90d816b3d35dcb6e00cc2fe0ac3c5cdb Mon Sep 17 00:00:00 2001 From: Stan Grams Date: Sat, 14 Mar 2026 13:17:45 +0100 Subject: [PATCH] [fix](trx-frontend-http): reset scheduler step timing Track the last applied scheduler entry so previous/next\ncycles correctly across active entries and resets the\ncountdown after manual entry changes.\n\nVerification: cargo test -p trx-frontend-http scheduler\nVerification: node --check assets/web/plugins/scheduler.js\n\nCo-authored-by: OpenAI Codex Signed-off-by: Stan Grams --- .../assets/web/plugins/scheduler.js | 36 ++++++++++++++++++- .../trx-frontend-http/src/scheduler.rs | 12 +++++-- 2 files changed, 45 insertions(+), 3 deletions(-) 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 eed6112..0ca2290 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 @@ -13,6 +13,7 @@ let schedulerRole = null; // "control" | "rx" | null let currentRigId = null; let currentConfig = null; + let currentSchedulerStatus = null; let bookmarkList = []; // [{id, name, freq_hz, mode}, ...] let statusInterval = null; let interleaveTicker = null; @@ -211,6 +212,30 @@ if (!(cycleMin > 0)) { return { activeEntries: active, currentIndex: 0, remainingSec: 0, cycleMin: 0 }; } + const statusEntryId = currentSchedulerStatus && currentSchedulerStatus.last_entry_id + ? String(currentSchedulerStatus.last_entry_id) + : ""; + const statusIndex = statusEntryId + ? active.findIndex(function (entry) { return String(entry && entry.id || "") === statusEntryId; }) + : -1; + const statusAppliedUtc = currentSchedulerStatus && Number.isFinite(Number(currentSchedulerStatus.last_applied_utc)) + ? Number(currentSchedulerStatus.last_applied_utc) + : null; + if (statusIndex >= 0 && statusAppliedUtc != null) { + const manualDurationMin = durations[statusIndex]; + const elapsedSec = Math.max(0, schedulerUtcSeconds() - statusAppliedUtc); + const remainingSec = (manualDurationMin > 0) + ? Math.max(1, (manualDurationMin * 60) - elapsedSec) + : 0; + if (remainingSec > 0) { + return { + activeEntries: active, + currentIndex: statusIndex, + remainingSec: remainingSec, + cycleMin: cycleMin, + }; + } + } const overlapStart = active.reduce(function (maxStart, entry) { return Math.max(maxStart, schedulerEntryCurrentWindowStart(entry, nowMin)); }, Number.NEGATIVE_INFINITY); @@ -285,7 +310,9 @@ if (!rig) return; apiGetStatus(rig) .then(function (st) { + currentSchedulerStatus = st || null; renderStatus(st); + renderSchedulerInterleaveStatus(); }) .catch(function () {}); } @@ -297,7 +324,13 @@ el.textContent = "No activity yet."; return; } - const name = st.last_bookmark_name || st.last_bookmark_id || "—"; + const statusEntryId = st.last_entry_id ? String(st.last_entry_id) : ""; + const entry = statusEntryId && currentConfig && Array.isArray(currentConfig.entries) + ? currentConfig.entries.find(function (item) { return String(item && item.id || "") === statusEntryId; }) + : null; + const name = entry + ? schedulerEntryDisplayName(entry) + : (st.last_bookmark_name || st.last_bookmark_id || "—"); let ts = ""; if (st.last_applied_utc) { const d = new Date(st.last_applied_utc * 1000); @@ -489,6 +522,7 @@ return apiActivateSchedulerEntry(currentRigId, target.id); }) .then(function (status) { + currentSchedulerStatus = status || null; renderStatus(status); renderSchedulerInterleaveStatus(); showSchedulerToast("Selected " + schedulerEntryDisplayName(target) + "."); 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 0d380c7..dbcf4bc 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 @@ -398,6 +398,8 @@ fn utc_minutes_now() -> f64 { #[derive(Debug, Clone, Serialize, Default)] pub struct SchedulerStatus { pub active: bool, + #[serde(skip_serializing_if = "Option::is_none")] + pub last_entry_id: Option, pub last_bookmark_id: Option, pub last_bookmark_name: Option, pub last_applied_utc: Option, @@ -414,6 +416,7 @@ async fn apply_scheduler_target( rig_id: &str, status_map: &SchedulerStatusMap, bookmarks: &BookmarkStore, + entry_id: Option<&str>, bookmark_id: &str, center_hz: Option, extra_bm_ids: &[String], @@ -456,6 +459,7 @@ async fn apply_scheduler_target( let status = SchedulerStatus { active: true, + last_entry_id: entry_id.map(str::to_string), last_bookmark_id: Some(bookmark_id.to_string()), last_bookmark_name: Some(bookmark.name.clone()), last_applied_utc: Some( @@ -586,7 +590,7 @@ pub fn spawn_scheduler_task( continue; } - let (bm_id, center_hz, extra_bm_ids) = match &config.mode { + let (entry_id, bm_id, center_hz, extra_bm_ids) = match &config.mode { SchedulerMode::Disabled => continue, SchedulerMode::Grayline => { let Some(bm_id) = config @@ -596,7 +600,7 @@ pub fn spawn_scheduler_task( else { continue; }; - (bm_id, None, Vec::new()) + (None, bm_id, None, Vec::new()) } SchedulerMode::TimeSpan => { let Some(entry) = @@ -605,6 +609,7 @@ pub fn spawn_scheduler_task( continue; }; ( + Some(entry.id.clone()), entry.bookmark_id.clone(), entry.center_hz, entry.bookmark_ids.clone(), @@ -641,6 +646,7 @@ pub fn spawn_scheduler_task( &config.rig_id, &status_map, &bookmarks, + entry_id.as_deref(), &bm_id, center_hz, &extra_bm_ids, @@ -731,6 +737,7 @@ async fn apply_last_scheduler_cycle( rig_id, status_map, bookmarks, + status.last_entry_id.as_deref(), &bookmark_id, status.last_center_hz, &status.last_bookmark_ids, @@ -858,6 +865,7 @@ pub async fn put_scheduler_activate_entry( &rig_id, status_map.get_ref(), bookmarks.get_ref().as_ref(), + Some(&entry.id), &entry.bookmark_id, entry.center_hz, &entry.bookmark_ids,