From 55e42315209ed96e3e410a17e9c507c972359e42 Mon Sep 17 00:00:00 2001 From: EMRG Evolution Date: Fri, 21 Aug 2026 17:47:16 +0800 Subject: [PATCH] emrg: refresh GUI task table immediately after trigger MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Rant 2026-08-21T17:41:23: clicking a task row's trigger button in the tasks panel only shows a toast — the table's status/last-run/next-run countdown stay stale until the panel is reopened, even though the daemon scheduler starts the task. Fix: re-render the task list right after a successful trigger. - dialogs.js trigger button: await renderTaskList() after 'triggered' and 'running' outcomes (error path unchanged; delete handler already refreshed, this closes the gap). - dialogs.js: new refreshTaskListIfOpen() helper — re-renders only when the tasks view is active (avoids a pointless re-fetch + countdown timer when the panel is hidden). - app.js doTrigger (/trigger from chat): call Dialogs.refreshTaskListIfOpen() after success so an open panel updates. Test: renderer.smoke.test.js trigger test now uses a stateful listTasks mock (trigger flips nightly to running) and asserts listTasks is re-fetched and the nightly row shows the running badge + disabled button after trigger; error path asserts no re-fetch. --- emrg/gui/renderer/js/app.js | 2 ++ emrg/gui/renderer/js/dialogs.js | 11 +++++++++++ emrg/gui/test/renderer.smoke.test.js | 25 ++++++++++++++++++++----- 3 files changed, 33 insertions(+), 5 deletions(-) diff --git a/emrg/gui/renderer/js/app.js b/emrg/gui/renderer/js/app.js index 77f4486..33f82c0 100644 --- a/emrg/gui/renderer/js/app.js +++ b/emrg/gui/renderer/js/app.js @@ -467,9 +467,11 @@ const App = (() => { } else if (res.result === "running") { Chat.addSystemMessage(_t("app.taskRunning", { n })); showToast(_t("app.taskRunning", { n }), { type: "info", durationMs: 4000 }); + await Dialogs.refreshTaskListIfOpen(); // rant 2026-08-21T17:41:23:面板开着则立即刷新 } else { Chat.addSystemMessage(_t("app.triggered", { n })); showToast(_t("app.triggered", { n }), { type: "success" }); + await Dialogs.refreshTaskListIfOpen(); // rant 2026-08-21T17:41:23:面板开着则立即刷新 } } catch (e) { Chat.addSystemMessage(_t("app.triggerFailed", { msg: e.message })); diff --git a/emrg/gui/renderer/js/dialogs.js b/emrg/gui/renderer/js/dialogs.js index 72ecd90..4844f9d 100644 --- a/emrg/gui/renderer/js/dialogs.js +++ b/emrg/gui/renderer/js/dialogs.js @@ -642,6 +642,14 @@ const Dialogs = (() => { return wrap; } + async function refreshTaskListIfOpen() { + // rant 2026-08-21T17:41:23:触发任务后表格不刷新(状态/上次运行/倒计时滞后)。 + // 聊天 /trigger 路径复用:任务面板开着才重渲染;没开就不做(renderTaskList 无谓开销 + 倒计时定时器)。 + if (App.state.activeView === "tasks") { + await renderTaskList(); + } + } + async function renderTaskList() { const list = $("task-list"); if (!list) return; // 元素缺失(测试桩)时忽略 @@ -713,9 +721,11 @@ const Dialogs = (() => { } else if (res && res.result === "running") { Chat.addSystemMessage(_t("app.taskRunning", { n: t.name })); showToast(_t("app.taskRunning", { n: t.name }), { type: "info", durationMs: 4000 }); + await renderTaskList(); // rant 2026-08-21T17:41:23:触发后立即刷新表格(状态/上次运行/倒计时不滞后) } else { Chat.addSystemMessage(_t("app.triggered", { n: t.name })); showToast(_t("app.triggered", { n: t.name }), { type: "success" }); + await renderTaskList(); // rant 2026-08-21T17:41:23:触发成功后立即刷新表格 } } catch (e) { Chat.addSystemMessage(_t("app.triggerFailed", { msg: e.message })); @@ -1573,6 +1583,7 @@ const Dialogs = (() => { initTaskManagement, // rant 18:23:15 P3:定时任务/自定义类型管理初始化 loadTaskMeta, // rant 18:23:15 P3:任务/类型元数据加载(面板打开/测试复用) renderTaskList, // rant 18:23:15 P3:任务列表渲染(测试/刷新复用) + refreshTaskListIfOpen, // rant 2026-08-21T17:41:23:任务面板开着才重渲染(聊天 /trigger 路径复用) formatCountdown, // rant 10:36:39:倒计时格式化(≤60s "43s" / ≤1h "1m23s" / >1h "1h05m";测试复用) updateTaskCountdowns, // rant 10:36:39:1s tick 更新倒计时文本(测试直接调用模拟走秒) startTaskCountdown, // rant 10:36:39:启动 1s 倒计时(幂等;renderTaskList 自动调用) diff --git a/emrg/gui/test/renderer.smoke.test.js b/emrg/gui/test/renderer.smoke.test.js index cdf8e89..241f700 100644 --- a/emrg/gui/test/renderer.smoke.test.js +++ b/emrg/gui/test/renderer.smoke.test.js @@ -2797,16 +2797,22 @@ test("P3:新增任务表单 —— 间隔 <60 客户端拒绝;≥60 提交 t test("rant 2026-08-15T09:20:27/09:23:10:面板操作反馈走全局 toast(任意视图可见)+ Trigger 三态语义", async () => { const triggerCalls = []; + let listCalls = 0; + let nightlyRunning = false; // rant 2026-08-21T17:41:23:状态化 mock —— 触发后 nightly 转为 running const { ctx, els } = makeSandbox({ - listTasks: async () => [ - { name: "emrg-task", type: "evolution", running: true, interval: 60 }, - { name: "nightly", type: "custom", running: false, interval: 3600 }, - ], + listTasks: async () => { + listCalls += 1; + return [ + { name: "emrg-task", type: "evolution", running: true, interval: 60 }, + { name: "nightly", type: "custom", running: nightlyRunning, interval: 3600 }, + ]; + }, listProjects: async () => [{ name: "emrg", path: "/p/emrg" }], triggerTask: async (payload) => { triggerCalls.push(payload); if (payload.name === "emrg-task") return { name: "emrg-task", result: "running", detail: "task is currently executing" }; if (payload.name === "missing") return { error: "task 'missing' not found" }; + nightlyRunning = true; // 触发成功 → daemon 侧状态变化(mock 模拟下次 listTasks 返回 running) return { name: "nightly", result: "triggered", detail: "next run moved to immediately" }; }, }); @@ -2822,6 +2828,7 @@ test("rant 2026-08-15T09:20:27/09:23:10:面板操作反馈走全局 toast( const btns1 = rows[1].querySelectorAll(".model-action-btn"); assert.strictEqual(btns0[0].disabled, true, "running 任务触发按钮应禁用"); assert.strictEqual(btns1[0].disabled, false, "空闲任务触发按钮可用"); + const callsBefore = listCalls; // 触发空闲任务 → triggered → success toast(面板视图下 toast 可见) btns1[0].click(); await tick(); @@ -2829,15 +2836,23 @@ test("rant 2026-08-15T09:20:27/09:23:10:面板操作反馈走全局 toast( assert.strictEqual(triggerCalls[0].name, "nightly"); assert.strictEqual(els["toast"].classList.contains("toast-success"), true, "triggered → success toast"); assert.ok(String(els["toast-msg"].textContent).includes("nightly"), "toast 消息含任务名"); + // rant 2026-08-21T17:41:23:触发成功后表格必须立即刷新 —— listTasks 被重拉 + nightly 行出现 running 徽标 + assert.ok(listCalls >= callsBefore + 1, "触发后应重新拉取任务列表(renderTaskList)"); + const rows2 = vm.runInContext('Array.from(document.getElementById("task-list").children).filter((c) => c.className.includes("task-row"))', ctx); + assert.strictEqual(rows2.length, 2, "刷新后仍两行"); + assert.strictEqual(rows2[1].querySelectorAll(".task-running-badge").length, 1, "触发后 nightly 行应显示 running 徽标"); + assert.strictEqual(rows2[1].querySelectorAll(".model-action-btn")[0].disabled, true, "触发后 nightly 触发按钮应禁用"); // /trigger 路径:running → info toast(不再假成功) await vm.runInContext('App.doTrigger("emrg-task")', ctx); await tick(); assert.strictEqual(els["toast"].classList.contains("toast-info"), true, "running → info toast"); assert.ok(String(els["toast-msg"].textContent).includes("emrg-task"), "info toast 含任务名"); - // 触发失败 → error toast + // 触发失败 → error toast(不刷新列表) + const callsBeforeErr = listCalls; vm.runInContext('App.doTrigger("missing")', ctx); await tick(); assert.strictEqual(els["toast"].classList.contains("toast-error"), true, "error → error toast"); + assert.strictEqual(listCalls, callsBeforeErr, "触发失败不应重拉列表"); }); // ── rant 2026-08-15T10:36:39:任务状态 + 下次运行倒计时 ──