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:任务状态 + 下次运行倒计时 ──