From 7d57bce5decb341a05de47111b05dd0300d76b03 Mon Sep 17 00:00:00 2001 From: wangdequan Date: Sun, 14 Jun 2026 21:48:43 +0800 Subject: [PATCH] =?UTF-8?q?=E6=8E=A8=E8=BF=9B=20INI=20=E9=9D=A2=E6=9D=BF?= =?UTF-8?q?=20machine=20session=20state=20snapshot=20helper?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- text11.txt | 72 ++++++++++ wasm-port/docs/ui-panel-state-summary.md | 34 +++++ wasm-port/runtime/ui/ini-panel/app.js | 127 +++++++++++++++++- .../ui/ini-panel/panel-state-summary.js | 42 ++++++ wasm-port/tests/browser/ini_panel_smoke.html | 31 +++++ .../node/verify_ini_panel_state_summary.mjs | 107 ++++++++++++++- 6 files changed, 410 insertions(+), 3 deletions(-) diff --git a/text11.txt b/text11.txt index 81dcd16..e8a707f 100644 --- a/text11.txt +++ b/text11.txt @@ -451,3 +451,75 @@ host_wasm_opfs_browser_smokes=ok 更完整的 machine session state snapshot helper,供上层 UI/API 直接消费当前 machine、 session、loaded WASM mapping、selected G-code、last run summary、panel badges 和 error state;该 helper 只聚合已有 host/runtime boundary facts,不解析 G-code 或扩展 CNC 语义。 + +三、2026-06-14 继续执行记录:INI panel machine session state snapshot helper + +本轮继续按 `text11.txt` 第一原则推进实质性 UI/API 能力,把 save/restore/load/run workflow +的结果汇总成一个 machine session state snapshot,供上层 UI/API 直接读取,而不是继续 +依赖分散的 DOM 字段、日志文本和临时全局值。 + +完成内容: + +- 扩展 `wasm-port/runtime/ui/ini-panel/panel-state-summary.js`; +- 新增导出 `createMachineSessionStateSnapshot(input)`; +- state snapshot 汇总: + - panel badges; + - machine; + - machineFiles; + - sessionSnapshot; + - sessionLoad; + - selectedProgram; + - run summary; + - fields; + - lastAction; + - error; + - fiveaxisRemap; +- `wasm-port/runtime/ui/ini-panel/app.js` 在 save/restore/load/run/failure/boot 路径后更新 + `window.linuxCncIniPanelMachineSessionState`; +- browser INI panel smoke 现在直接校验该 state snapshot 的关键字段; +- `wasm-port/tests/ui/node/verify_ini_panel_state_summary.mjs` 覆盖新 state snapshot 形状; +- `wasm-port/docs/ui-panel-state-summary.md` 更新 state snapshot API、window 暴露和边界说明; +- 未改变 OPFS bridge、snapshot-store、machine-file-store、LinuxCNC interpreter、G-code 文本、 + canonical-event 语义、tool、parameter、planner 或 LinuxCNC-owned runtime semantics。 + +验证已通过: + +```bash +git diff --check +wasm-port/tests/ui/node/verify_ini_panel_state_summary.sh +wasm-port/tests/ui/node/verify_ui_node_smokes.sh +SKIP_INI_BUILD=1 SKIP_INTERP_BUILD=1 wasm-port/tests/browser/verify_ini_panel_browser.sh +wasm-port/tools/verify_vendor_sync.sh +wasm-port/tools/verify_no_standalone_cnc_semantics.sh +SKIP_INTERP_BUILD=1 wasm-port/tests/wasm/node/verify_interp_wasm.sh +SKIP_INTERP_BUILD=1 wasm-port/tests/wasm/node/verify_sim_configs_inventory_wasm.sh +wasm-port/tests/host/verify_host_smokes.sh +``` + +关键输出: + +```text +ini_panel_state_summary_node_smoke=ok +ini_panel_run_summary_node_smoke=ok +ini_panel_run_workflow_node_smoke=ok +ini_panel_session_summary_node_smoke=ok +ini_panel_session_workflow_node_smoke=ok +ini_panel_machine_file_summary_node_smoke=ok +ui_node_smokes=ok +browser_ini_opfs_smoke=ok +vendor sync up to date +standalone CNC semantics guard complete +interp_wasm_node_smoke=ok +sim_configs_wasm_node_inventory_executed=28 +sim_configs_wasm_node_inventory_passed=28 +sim_configs_wasm_node_inventory_skipped=131 +sim_configs_wasm_node_inventory_unexpected_fail=0 +host_wasm_opfs_browser_smokes=ok +``` + +下一步建议: + +继续按“实质性能力优先”推进。下一批优先把 `window.linuxCncIniPanelMachineSessionState` +整理成更明确的 API/文档使用方式,或者在 `run-gcode`/restore/load/save 之外再补一层 +更高阶的 UI consumer helper,用于读取状态并生成上层展示/导出结果;只围绕已有 +host/runtime boundary facts 组合,不回到 path guard。 diff --git a/wasm-port/docs/ui-panel-state-summary.md b/wasm-port/docs/ui-panel-state-summary.md index 7587f1f..caae3d8 100644 --- a/wasm-port/docs/ui-panel-state-summary.md +++ b/wasm-port/docs/ui-panel-state-summary.md @@ -30,11 +30,45 @@ reading ad hoc DOM text. The returned snapshot preserves those nested objects and normalizes missing badge entries to `null`. +## `createMachineSessionStateSnapshot(input)` + +This helper builds the richer state snapshot used by the INI panel and exposed +as `window.linuxCncIniPanelMachineSessionState`. + +It preserves the base panel state and adds: + +```js +{ + selectedProgram: { source: "snapshot", opfsPath: "...", wasmPath: "..." }, + fields: { + sessionIni: "/work/session-machine.ini", + sessionParameters: "/work/session-linuxcnc.var", + sessionToolTable: "/work/session-tool.tbl", + sessionGcode: "linuxcnc/gcode/ui-session.ngc", + sessionSnapshot: "ui-machine-session/ui-machine-session.json", + runStatus: "ok", + runSource: "snapshot", + runSnapshot: "ui-machine-session/ui-machine-session.json", + runWorkflow: "save-session-snapshot", + runOpfsPath: "linuxcnc/gcode/ui-session.ngc", + runWasmPath: "/work/ui-session.ngc" + }, + lastAction: "run-gcode", + error: null +} +``` + +The INI panel updates this state after save, restore, load, run, and error +paths so browser tests and future API consumers can read one machine-readable +state object instead of collecting DOM fields and log text. + ## Boundary Rules - Do not parse G-code text in this helper. - Do not infer CNC semantics from the nested summaries. - Keep the helper as a bundling layer over already-observed UI boundary state. +- Keep workflow result objects as host/runtime boundary facts; this helper must + not promote, reinterpret, or synthesize CNC behavior. ## Validation diff --git a/wasm-port/runtime/ui/ini-panel/app.js b/wasm-port/runtime/ui/ini-panel/app.js index 393bfe5..00c5133 100644 --- a/wasm-port/runtime/ui/ini-panel/app.js +++ b/wasm-port/runtime/ui/ini-panel/app.js @@ -45,6 +45,9 @@ import { restoreMachineSessionSnapshotWorkflow, saveMachineSessionSnapshotWorkflow, } from "./session-workflow.js"; +import { + createMachineSessionStateSnapshot, +} from "./panel-state-summary.js"; const SAMPLE_PATH = "../../../vendor/linuxcnc/configs/sim/axis/vismach/5axis/table-dual-rotary/xyzab-tdr.ini"; @@ -146,6 +149,20 @@ let loadedSession = null; let restoredSessionSnapshot = null; let canonicalEventText = ""; let runPlaybackTimer = null; +let panelMachineSessionState = createMachineSessionStateSnapshot(); + +function updatePanelMachineSessionState(patch = {}) { + panelMachineSessionState = createMachineSessionStateSnapshot({ + ...panelMachineSessionState, + ...patch, + fields: { + ...panelMachineSessionState.fields, + ...(patch.fields ?? {}), + }, + }); + window.linuxCncIniPanelMachineSessionState = panelMachineSessionState; + return panelMachineSessionState; +} function setBadge(node, text, className = "badge") { node.className = className; @@ -377,6 +394,17 @@ function setLastRunSummary(summary) { setField("runWorkflow", summary.workflow ?? "-"); setField("runOpfsPath", summary.programOpfsPath); setField("runWasmPath", summary.programWasmPath); + updatePanelMachineSessionState({ + run: summary, + fields: { + runStatus: summary.runStatus, + runSource: summary.programSource, + runSnapshot: summary.snapshotLabel ?? "-", + runWorkflow: summary.workflow ?? "-", + runOpfsPath: summary.programOpfsPath, + runWasmPath: summary.programWasmPath, + }, + }); return summary; } @@ -463,27 +491,45 @@ async function boot() { try { iniSdk = await createLinuxCncIniSdk(); setBadge(wasmBadge, "INI WASM: ready"); + updatePanelMachineSessionState({ badges: { ...panelMachineSessionState.badges, ini: "INI WASM: ready" } }); } catch (error) { setBadge(wasmBadge, "INI WASM: failed", "badge danger"); setLog(`INI WASM init failed: ${error.message}`, true); + updatePanelMachineSessionState({ + badges: { ...panelMachineSessionState.badges, ini: "INI WASM: failed" }, + lastAction: "boot", + error: error.message, + }); throw error; } try { interpSdk = await createLinuxCncInterpSdk(); setBadge(interpBadge, "Interpreter WASM: ready"); + updatePanelMachineSessionState({ badges: { ...panelMachineSessionState.badges, interp: "Interpreter WASM: ready" } }); } catch (error) { setBadge(interpBadge, "Interpreter WASM: failed", "badge danger"); setLog(`Interpreter WASM init failed: ${error.message}`, true); + updatePanelMachineSessionState({ + badges: { ...panelMachineSessionState.badges, interp: "Interpreter WASM: failed" }, + lastAction: "boot", + error: error.message, + }); throw error; } try { await getOpfsRoot(); setBadge(opfsBadge, "OPFS: ready"); + updatePanelMachineSessionState({ badges: { ...panelMachineSessionState.badges, opfs: "OPFS: ready" } }); } catch (error) { setBadge(opfsBadge, "OPFS: unavailable", "badge danger"); setLog(`OPFS check failed: ${error.message}`, true); + updatePanelMachineSessionState({ + badges: { ...panelMachineSessionState.badges, opfs: "OPFS: unavailable" }, + lastAction: "boot", + error: error.message, + }); } } @@ -513,6 +559,11 @@ document.getElementById("save-machine-files").addEventListener("click", async () }); const gcodePath = await saveGcodeProgram(UI_SESSION.defaultGcodeFilename, DEFAULT_GCODE); const summary = createMachineFileSaveSummary(paths, gcodePath); + updatePanelMachineSessionState({ + machineFiles: summary, + lastAction: "save-machine-files", + error: null, + }); setLog( [ "Saved machine text files to OPFS.", @@ -544,6 +595,15 @@ document.getElementById("save-session-snapshot").addEventListener("click", async for (const [name, value] of Object.entries(workflow.fields)) { setField(name, value); } + restoredSessionSnapshot = workflow.snapshot; + updatePanelMachineSessionState({ + sessionSnapshot: workflow.summary, + sessionLoad: null, + selectedProgram: null, + fields: workflow.fields, + lastAction: "save-session-snapshot", + error: null, + }); setLog( [ "Saved machine session snapshot to OPFS.", @@ -551,6 +611,10 @@ document.getElementById("save-session-snapshot").addEventListener("click", async ].join("\n"), ); } catch (error) { + updatePanelMachineSessionState({ + lastAction: "save-session-snapshot", + error: error.message, + }); setLog(`Machine session snapshot save failed: ${error.message}`, true); } }); @@ -571,6 +635,15 @@ document.getElementById("restore-session-snapshot").addEventListener("click", as setField("sessionToolTable", workflow.fields.sessionToolTable); setField("sessionGcode", workflow.fields.sessionGcode); setField("sessionSnapshot", workflow.fields.sessionSnapshot); + restoredSessionSnapshot = workflow.snapshot; + updatePanelMachineSessionState({ + sessionSnapshot: workflow.summary, + sessionLoad: null, + selectedProgram: null, + fields: workflow.fields, + lastAction: "restore-session-snapshot", + error: null, + }); setLog( [ "Restored machine session snapshot from OPFS.", @@ -578,6 +651,10 @@ document.getElementById("restore-session-snapshot").addEventListener("click", as ].join("\n"), ); } catch (error) { + updatePanelMachineSessionState({ + lastAction: "restore-session-snapshot", + error: error.message, + }); setLog(`Machine session snapshot restore failed: ${error.message}`, true); } }); @@ -586,6 +663,16 @@ document.getElementById("load-session").addEventListener("click", async () => { try { await loadMachineSessionIntoWasm(); const loadSummary = createMachineSessionLoadSummary(loadedSession); + updatePanelMachineSessionState({ + sessionLoad: loadSummary, + fields: { + sessionIni: loadSummary.iniWasmPath, + sessionParameters: loadSummary.parameterWasmPath, + sessionToolTable: loadSummary.toolTableWasmPath, + }, + lastAction: "load-session", + error: null, + }); setLog( [ "Loaded machine session into the interpreter WASM filesystem.", @@ -593,6 +680,10 @@ document.getElementById("load-session").addEventListener("click", async () => { ].join("\n"), ); } catch (error) { + updatePanelMachineSessionState({ + lastAction: "load-session", + error: error.message, + }); setLog(`Machine session load failed: ${error.message}`, true); } }); @@ -614,6 +705,18 @@ document.getElementById("restore-load-session").addEventListener("click", async setField("sessionToolTable", workflow.fields.sessionToolTable); setField("sessionGcode", workflow.fields.sessionGcode); setField("sessionSnapshot", workflow.fields.sessionSnapshot); + restoredSessionSnapshot = workflow.snapshot; + if (workflow.loadedSession) { + loadedSession = workflow.loadedSession; + } + updatePanelMachineSessionState({ + sessionSnapshot: workflow.summary, + sessionLoad: workflow.loadSummary, + selectedProgram: null, + fields: workflow.fields, + lastAction: "restore-load-session", + error: null, + }); setLog( [ "Restored and loaded machine session snapshot.", @@ -621,6 +724,10 @@ document.getElementById("restore-load-session").addEventListener("click", async ].join("\n"), ); } catch (error) { + updatePanelMachineSessionState({ + lastAction: "restore-load-session", + error: error.message, + }); setLog(`Machine session snapshot restore/load failed: ${error.message}`, true); } }); @@ -631,8 +738,6 @@ document.getElementById("run-gcode").addEventListener("click", async () => { throw new Error("Load a machine session before running G-code."); } const interp = requireInterpSdk(); - const program = await loadSelectedGcodeProgram(); - interp.writeTextFile(program.wasmPath, program.text); clearRunPlayback(); setField("runStatus", "running"); clearLastRunSummary(); @@ -659,6 +764,17 @@ document.getElementById("run-gcode").addEventListener("click", async () => { setField("sessionGcode", workflow.fields.sessionGcode); setField("runStatus", workflow.fields.runStatus); setLastRunSummary(workflow.summary); + updatePanelMachineSessionState({ + selectedProgram: { + source: workflow.program.source, + opfsPath: workflow.program.opfsPath, + wasmPath: workflow.program.wasmPath, + }, + run: workflow.summary, + fields: workflow.fields, + lastAction: "run-gcode", + error: null, + }); setCanonicalEvents(workflow.resultText); playRunMotion(workflow.resultText, workflow.program.text, workflow.motionSnapshots); setLog( @@ -673,6 +789,13 @@ document.getElementById("run-gcode").addEventListener("click", async () => { clearRunPlayback(); clearLastRunSummary(); setRunMonitor(0, "failed", {}, "failed"); + updatePanelMachineSessionState({ + fields: { + runStatus: "failed", + }, + lastAction: "run-gcode", + error: error.message, + }); setLog(`G-code run failed: ${error.message}`, true); } }); diff --git a/wasm-port/runtime/ui/ini-panel/panel-state-summary.js b/wasm-port/runtime/ui/ini-panel/panel-state-summary.js index 56f6818..d7cc507 100644 --- a/wasm-port/runtime/ui/ini-panel/panel-state-summary.js +++ b/wasm-port/runtime/ui/ini-panel/panel-state-summary.js @@ -21,3 +21,45 @@ export function createIniPanelStateSummary({ fiveaxisRemap: fiveaxisRemap ?? null, }; } + +export function createMachineSessionStateSnapshot({ + badges = {}, + machine = null, + machineFiles = null, + sessionSnapshot = null, + sessionLoad = null, + selectedProgram = null, + run = null, + fields = {}, + lastAction = null, + error = null, + fiveaxisRemap = null, +} = {}) { + return { + ...createIniPanelStateSummary({ + badges, + machine, + machineFiles, + sessionSnapshot, + sessionLoad, + run, + fiveaxisRemap, + }), + selectedProgram, + fields: { + sessionIni: fields.sessionIni ?? null, + sessionParameters: fields.sessionParameters ?? null, + sessionToolTable: fields.sessionToolTable ?? null, + sessionGcode: fields.sessionGcode ?? null, + sessionSnapshot: fields.sessionSnapshot ?? null, + runStatus: fields.runStatus ?? null, + runSource: fields.runSource ?? null, + runSnapshot: fields.runSnapshot ?? null, + runWorkflow: fields.runWorkflow ?? null, + runOpfsPath: fields.runOpfsPath ?? null, + runWasmPath: fields.runWasmPath ?? null, + }, + lastAction: lastAction ?? null, + error: error ?? null, + }; +} diff --git a/wasm-port/tests/browser/ini_panel_smoke.html b/wasm-port/tests/browser/ini_panel_smoke.html index a6ea383..aa80ac6 100644 --- a/wasm-port/tests/browser/ini_panel_smoke.html +++ b/wasm-port/tests/browser/ini_panel_smoke.html @@ -660,6 +660,37 @@ TOOL_TABLE = browser-tool.tbl 2, "UI snapshot run summary motion snapshot count", ); + const machineSessionState = uiDocument.defaultView.linuxCncIniPanelMachineSessionState; + assertEqual( + machineSessionState.lastAction, + "run-gcode", + "UI machine session state last action", + ); + assertEqual( + machineSessionState.selectedProgram.opfsPath, + "linuxcnc/gcode/ui-session.ngc", + "UI machine session state selected program OPFS path", + ); + assertEqual( + machineSessionState.sessionLoad.iniWasmPath, + "/work/session-machine.ini", + "UI machine session state loaded INI WASM path", + ); + assertEqual( + machineSessionState.run.programSource, + "snapshot", + "UI machine session state run source", + ); + assertEqual( + machineSessionState.fields.runWorkflow, + "save-session-snapshot", + "UI machine session state run workflow field", + ); + assertEqual( + machineSessionState.error, + null, + "UI machine session state error", + ); if ( !runLog.includes("canon_event=STRAIGHT_TRAVERSE") || !runLog.includes("canon_event=STRAIGHT_FEED") diff --git a/wasm-port/tests/ui/node/verify_ini_panel_state_summary.mjs b/wasm-port/tests/ui/node/verify_ini_panel_state_summary.mjs index 93c7efa..6926040 100644 --- a/wasm-port/tests/ui/node/verify_ini_panel_state_summary.mjs +++ b/wasm-port/tests/ui/node/verify_ini_panel_state_summary.mjs @@ -1,6 +1,9 @@ import assert from "node:assert/strict"; -import { createIniPanelStateSummary } from "../../../runtime/ui/ini-panel/panel-state-summary.js"; +import { + createIniPanelStateSummary, + createMachineSessionStateSnapshot, +} from "../../../runtime/ui/ini-panel/panel-state-summary.js"; const summary = createIniPanelStateSummary({ badges: { @@ -81,4 +84,106 @@ assert.deepEqual( }, ); +assert.deepEqual( + createMachineSessionStateSnapshot({ + badges: { + ini: "INI WASM: ready", + interp: "Interpreter WASM: ready", + opfs: "OPFS: ready", + }, + machine: { + machine: "xyzab-tdr", + }, + sessionSnapshot: { + snapshotLabel: "ui-machine-session/ui-machine-session.json", + }, + sessionLoad: { + iniWasmPath: "/work/session-machine.ini", + }, + selectedProgram: { + source: "snapshot", + opfsPath: "linuxcnc/gcode/ui-session.ngc", + wasmPath: "/work/ui-session.ngc", + }, + run: { + runStatus: "ok", + programSource: "snapshot", + }, + fields: { + sessionIni: "/work/session-machine.ini", + sessionParameters: "/work/session-linuxcnc.var", + sessionToolTable: "/work/session-tool.tbl", + sessionGcode: "linuxcnc/gcode/ui-session.ngc", + sessionSnapshot: "ui-machine-session/ui-machine-session.json", + runStatus: "ok", + runSource: "snapshot", + runSnapshot: "ui-machine-session/ui-machine-session.json", + runWorkflow: "save-session-snapshot", + runOpfsPath: "linuxcnc/gcode/ui-session.ngc", + runWasmPath: "/work/ui-session.ngc", + }, + lastAction: "run-gcode", + fiveaxisRemap: "ok", + }), + { + badges: { + ini: "INI WASM: ready", + interp: "Interpreter WASM: ready", + opfs: "OPFS: ready", + }, + machine: { + machine: "xyzab-tdr", + }, + machineFiles: null, + sessionSnapshot: { + snapshotLabel: "ui-machine-session/ui-machine-session.json", + }, + sessionLoad: { + iniWasmPath: "/work/session-machine.ini", + }, + run: { + runStatus: "ok", + programSource: "snapshot", + }, + fiveaxisRemap: "ok", + selectedProgram: { + source: "snapshot", + opfsPath: "linuxcnc/gcode/ui-session.ngc", + wasmPath: "/work/ui-session.ngc", + }, + fields: { + sessionIni: "/work/session-machine.ini", + sessionParameters: "/work/session-linuxcnc.var", + sessionToolTable: "/work/session-tool.tbl", + sessionGcode: "linuxcnc/gcode/ui-session.ngc", + sessionSnapshot: "ui-machine-session/ui-machine-session.json", + runStatus: "ok", + runSource: "snapshot", + runSnapshot: "ui-machine-session/ui-machine-session.json", + runWorkflow: "save-session-snapshot", + runOpfsPath: "linuxcnc/gcode/ui-session.ngc", + runWasmPath: "/work/ui-session.ngc", + }, + lastAction: "run-gcode", + error: null, + }, +); + +assert.deepEqual( + createMachineSessionStateSnapshot().fields, + { + sessionIni: null, + sessionParameters: null, + sessionToolTable: null, + sessionGcode: null, + sessionSnapshot: null, + runStatus: null, + runSource: null, + runSnapshot: null, + runWorkflow: null, + runOpfsPath: null, + runWasmPath: null, + }, +); + console.log("ini_panel_state_summary_node_smoke=ok");