From 2ee93d19d19578d49e86a1d5c3151981bc6bab69 Mon Sep 17 00:00:00 2001 From: EMRG Evolution Date: Tue, 18 Aug 2026 13:37:34 +0800 Subject: [PATCH] emrg: GUI task countdown re-fetches state on expiry (rant 2026-08-18T11:16:32) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The tasks-panel countdown tick (updateTaskCountdowns) only decremented the remaining-time text and clamped at 0s — when a pending task's deadline passed, the UI never flipped to the running badge until a manual action re-rendered. Now a countdown hitting 0 triggers renderTaskList() once (per-entry expired flag prevents re-trigger loops), so pending → running transitions appear automatically. Smoke test extended: stateful mock flips waiting-task to running after the deadline and asserts the refresh. --- emrg/gui/renderer/js/dialogs.js | 10 ++++++++++ emrg/gui/test/renderer.smoke.test.js | 21 +++++++++++++++------ 2 files changed, 25 insertions(+), 6 deletions(-) diff --git a/emrg/gui/renderer/js/dialogs.js b/emrg/gui/renderer/js/dialogs.js index d2f7fa0e..4e84e3d2 100644 --- a/emrg/gui/renderer/js/dialogs.js +++ b/emrg/gui/renderer/js/dialogs.js @@ -639,9 +639,19 @@ const Dialogs = (() => { } function updateTaskCountdowns() { + let expired = false; for (const e of taskCountdowns) { const rem = Math.max(0, Math.ceil((e.deadline - Date.now()) / 1000)); e.span.textContent = _t("app.taskNextRun", { n: formatCountdown(rem) }); + // rant 2026-08-18T11:16:32:倒计时归零后任务状态不会自动更新(pending → running 永不反映到 UI)。 + // 检测到 deadline 已过 → 重新拉取任务状态;e.expired 防止同一任务重复触发。 + if (rem <= 0 && !e.expired) { + e.expired = true; + expired = true; + } + } + if (expired) { + renderTaskList().catch(() => {}); } } diff --git a/emrg/gui/test/renderer.smoke.test.js b/emrg/gui/test/renderer.smoke.test.js index 361d3de1..7c4ad285 100644 --- a/emrg/gui/test/renderer.smoke.test.js +++ b/emrg/gui/test/renderer.smoke.test.js @@ -2755,17 +2755,19 @@ test("rant 10:36:39:formatCountdown 格式边界(0s/59s/60s/1h05m/负数钳 assert.strictEqual(fmt(1.9), "1s", "小数向下取整"); }); -test("rant 10:36:39:任务行状态展示 —— 运行中/待运行+倒计时/待调度/已停用 + 每秒递减", async () => { +test("rant 10:36:39:任务行状态展示 —— 运行中/待运行+倒计时/待调度/已停用 + 每秒递减 + 归零自动刷新", async () => { + let nowMs = 1_700_000_000_000; const { ctx, win } = makeSandbox({ + // rant 11:16:32:mock 有状态 —— deadline(50s 后)已过 → waiting-task 转为 running, + // 验证归零后 updateTaskCountdowns 自动重拉任务状态(pending → running 徽标) listTasks: async () => [ { name: "running-task", type: "evolution", running: true, interval: 60, next_run_in_seconds: null }, - { name: "waiting-task", type: "evolution", running: false, interval: 1800, next_run_in_seconds: 43 }, + { name: "waiting-task", type: "evolution", running: nowMs >= 1_700_000_050_000, interval: 1800, next_run_in_seconds: 43 }, { name: "idle-task", type: "custom", running: false, interval: 3600, next_run_in_seconds: null, enabled: true }, { name: "disabled-task", type: "custom", running: false, interval: 3600, next_run_in_seconds: null, enabled: false }, ], listProjects: async () => [], }); - let nowMs = 1_700_000_000_000; win.Date = class extends Date { static now() { return nowMs; } }; await tick(); await vm.runInContext("App.openTasksPanel()", ctx); @@ -2788,11 +2790,18 @@ test("rant 10:36:39:任务行状态展示 —— 运行中/待运行+倒计时 await vm.runInContext("EMRG_Dialogs.updateTaskCountdowns()", ctx); let nextRuns = vm.runInContext(`Array.from(document.getElementById("task-list").children).map((r) => { const s = r.querySelector(".task-next-run"); return s ? s.textContent : ""; }).filter(Boolean)`, ctx); assert.deepStrictEqual(nextRuns, ["下次运行 42s"], "1s 后递减为 42s"); - // 快进到 deadline 之后 → 负数钳制为 0s(不显示负数) + // 快进到 deadline 之后 → rant 11:16:32:归零触发自动 renderTaskList 重拉状态(异步), + // mock 此刻返回 running → UI 从"待运行+倒计时"刷新为 running 徽标(无倒计时) nowMs += 50_000; await vm.runInContext("EMRG_Dialogs.updateTaskCountdowns()", ctx); - nextRuns = vm.runInContext(`Array.from(document.getElementById("task-list").children).map((r) => { const s = r.querySelector(".task-next-run"); return s ? s.textContent : ""; }).filter(Boolean)`, ctx); - assert.deepStrictEqual(nextRuns, ["下次运行 0s"], "deadline 过后钳制为 0s"); + await tick(); + const refreshed = vm.runInContext(`Array.from(document.getElementById("task-list").children).map((r) => { + const name = r.querySelector(".task-name") ? r.querySelector(".task-name").textContent : ""; + const badges = Array.from(r.querySelectorAll(".task-badge")).map((b) => b.textContent).join(","); + return name + "|" + badges; + })`, ctx); + assert.ok(refreshed.some((t) => t.startsWith("waiting-task") && t.includes("运行中") && !t.includes("下次运行")), + `归零后自动刷新为 running 徽标:${JSON.stringify(refreshed)}`); }); test("rant 10:36:39:倒计时生命周期 —— 渲染启动 interval、离开任务视图清理(无泄漏)", async () => {