From 8e8a319d7875a976dc5994a6b261d72954ad2c40 Mon Sep 17 00:00:00 2001 From: wangdequan Date: Sun, 14 Jun 2026 21:30:19 +0800 Subject: [PATCH] =?UTF-8?q?=E6=8E=A8=E8=BF=9B=20INI=20=E9=9D=A2=E6=9D=BF?= =?UTF-8?q?=20restore/load=20session=20workflow=20helper?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- text11.txt | 79 +++++++++ wasm-port/docs/ui-session-summary-helpers.md | 42 ++++- wasm-port/runtime/ui/ini-panel/app.js | 47 ++++-- .../runtime/ui/ini-panel/session-workflow.js | 65 ++++++++ .../verify_ini_panel_session_workflow.mjs | 153 +++++++++++++++++- 5 files changed, 364 insertions(+), 22 deletions(-) diff --git a/text11.txt b/text11.txt index 4f26350..564ba1f 100644 --- a/text11.txt +++ b/text11.txt @@ -302,3 +302,82 @@ wasm-port/tests/host/verify_host_smokes.sh 更新抽成可调用 workflow helper; - 给 UI/API 暴露一个更完整的 machine session state snapshot,用于上层应用直接消费; - 只在这些 workflow helper 的验证需要时,继续收敛局部测试胶水。 + +一、2026-06-14 继续执行记录:INI panel restore/load session workflow helper + +本轮按 `text11.txt` 第一原则继续推进实质性 UI/session persistence 能力,把 +`Restore Session Snapshot` 和 `Restore And Load Session` 两条用户流程抽成可复用 workflow +helper,而不是继续做局部 path/test glue 收敛。 + +完成内容: + +- 扩展 `wasm-port/runtime/ui/ini-panel/session-workflow.js`; +- 新增导出: + - `restoreMachineSessionSnapshotWorkflow(input)`; + - `sessionSnapshotRestoreLogLines(summary)`; +- restore workflow 通过注入函数执行: + - `loadMachineSessionSnapshot(...)`; + - `loadMachineTextFiles(...)`; + - 可选 `loadSession()`; +- restore-only 返回: + - restored snapshot; + - editor INI text; + - snapshot summary; + - OPFS field state; + - restore log lines; +- restore-and-load 返回: + - loaded session; + - load summary; + - WASM field state; + - snapshot + OPFS-to-WASM load log lines; +- `wasm-port/runtime/ui/ini-panel/app.js` 的 `Restore Session Snapshot` 和 + `Restore And Load Session` 按钮改为调用 workflow helper,按钮处理器只负责写 editor、 + field 和 log; +- `wasm-port/tests/ui/node/verify_ini_panel_session_workflow.mjs` 覆盖 save、restore-only + 和 restore-and-load 三条 workflow; +- `wasm-port/docs/ui-session-summary-helpers.md` 更新 restore workflow API、返回结构、 + log helper 和边界说明; +- 未改变 OPFS bridge、snapshot-store、machine-file-store、LinuxCNC interpreter、tool、 + parameter、G-code 文本或 LinuxCNC-owned runtime semantics。 + +验证已通过: + +```bash +git diff --check +wasm-port/tests/ui/node/verify_ini_panel_session_workflow.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_session_workflow_node_smoke=ok +ini_panel_run_summary_node_smoke=ok +ini_panel_session_summary_node_smoke=ok +ini_panel_machine_file_summary_node_smoke=ok +ini_panel_state_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 +``` + +下一步建议: + +继续按“实质性能力优先”推进。下一批优先把 `run-gcode` 的用户流程抽成可调用 workflow +helper,覆盖 snapshot/default program selection、G-code OPFS load、WASM write、LinuxCNC +interpreter run、run summary、field/log state 和 error state。该 helper 仍只能编排 +host/runtime boundary,不解析或实现 G-code、planner、tool、parameter 或 canonical-event +语义。 diff --git a/wasm-port/docs/ui-session-summary-helpers.md b/wasm-port/docs/ui-session-summary-helpers.md index 9cef416..03399d3 100644 --- a/wasm-port/docs/ui-session-summary-helpers.md +++ b/wasm-port/docs/ui-session-summary-helpers.md @@ -8,9 +8,9 @@ for summarizing machine session snapshot state. for machine-session load state. These helpers are pure so browser UI panels, SDK-facing wrappers, and Node smoke tests can share the same data shape without parsing log text. -`runtime/ui/ini-panel/session-workflow.js` contains the reusable UI workflow for -saving the machine text files, default G-code, and machine session snapshot as -one host-boundary operation. +`runtime/ui/ini-panel/session-workflow.js` contains the reusable UI workflows +for saving, restoring, and restore-loading a machine session snapshot as +host-boundary operations. The helpers do not implement CNC behavior. G-code execution, canonical events, tool handling, parameters, kinematics, and planner behavior remain owned by the @@ -72,6 +72,40 @@ The persistence functions are injected by the caller: } ``` +## `restoreMachineSessionSnapshotWorkflow(input)` + +The workflow loads the machine session snapshot, loads the persisted machine +text files so the editor can be restored, and optionally loads the machine +session into the interpreter WASM filesystem. + +When `loadSession` is omitted, the returned `fields` contain the OPFS snapshot +paths. When `loadSession` is supplied, the returned `fields` contain the loaded +WASM paths for INI/parameter/tool-table files and the snapshot G-code OPFS +path. + +The returned object has this shape: + +```js +{ + snapshot: { ... }, + editorIniText: "[EMC]\n...", + summary: { snapshotLabel: "...", workflow: "save-session-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" + }, + loadSummary: { iniWasmPath: "/work/session-machine.ini" }, + loadedSession: { ... }, + logLines: [ + "Snapshot: ui-machine-session/ui-machine-session.json", + "Workflow: save-session-snapshot" + ] +} +``` + The returned object has this shape: ```js @@ -102,6 +136,8 @@ The returned object has this shape: OPFS-to-WASM mapping and captured result lines. - `sessionSnapshotSaveLogLines(summary)` renders the save-workflow snapshot paths used by the INI panel. +- `sessionSnapshotRestoreLogLines(summary)` renders the restore-workflow + snapshot paths used by the INI panel. ## Boundary Rules diff --git a/wasm-port/runtime/ui/ini-panel/app.js b/wasm-port/runtime/ui/ini-panel/app.js index afa70b2..d7d0e99 100644 --- a/wasm-port/runtime/ui/ini-panel/app.js +++ b/wasm-port/runtime/ui/ini-panel/app.js @@ -42,6 +42,7 @@ import { sessionSnapshotSummaryLogLines, } from "./session-summary.js"; import { + restoreMachineSessionSnapshotWorkflow, saveMachineSessionSnapshotWorkflow, } from "./session-workflow.js"; @@ -600,16 +601,24 @@ document.getElementById("save-session-snapshot").addEventListener("click", async document.getElementById("restore-session-snapshot").addEventListener("click", async () => { try { - const snapshot = await restoreSessionSnapshotToEditor(); - const summary = createSessionSnapshotSummary(snapshot, sessionSnapshotLabel(snapshot.sessionId)); + const workflow = await restoreMachineSessionSnapshotWorkflow({ + machineId: MACHINE_ID, + snapshotId: UI_SESSION.snapshotId, + snapshotFilename: UI_SESSION.snapshotFilename, + snapshotLabel: sessionSnapshotLabel(UI_SESSION), + loadMachineSessionSnapshot, + loadMachineTextFiles, + }); + editor.value = workflow.editorIniText; + setField("sessionIni", workflow.fields.sessionIni); + setField("sessionParameters", workflow.fields.sessionParameters); + setField("sessionToolTable", workflow.fields.sessionToolTable); + setField("sessionGcode", workflow.fields.sessionGcode); + setField("sessionSnapshot", workflow.fields.sessionSnapshot); setLog( [ "Restored machine session snapshot from OPFS.", - ...sessionSnapshotSummaryLogLines(summary), - `INI: ${summary.iniOpfsPath}`, - `Parameter file: ${summary.parameterOpfsPath}`, - `Tool table file: ${summary.toolTableOpfsPath}`, - `G-code: ${summary.gcodeOpfsPath ?? "-"}`, + ...workflow.logLines, ].join("\n"), ); } catch (error) { @@ -634,17 +643,25 @@ document.getElementById("load-session").addEventListener("click", async () => { document.getElementById("restore-load-session").addEventListener("click", async () => { try { - const snapshot = await restoreSessionSnapshotToEditor(); - await loadMachineSessionIntoWasm(); - setField("sessionGcode", snapshot.payload.files.gcode); - const summary = createSessionSnapshotSummary(snapshot, sessionSnapshotLabel(snapshot.sessionId)); - const loadSummary = createMachineSessionLoadSummary(loadedSession); + const workflow = await restoreMachineSessionSnapshotWorkflow({ + machineId: MACHINE_ID, + snapshotId: UI_SESSION.snapshotId, + snapshotFilename: UI_SESSION.snapshotFilename, + snapshotLabel: sessionSnapshotLabel(UI_SESSION), + loadMachineSessionSnapshot, + loadMachineTextFiles, + loadSession: () => loadMachineSessionIntoWasm(), + }); + editor.value = workflow.editorIniText; + setField("sessionIni", workflow.fields.sessionIni); + setField("sessionParameters", workflow.fields.sessionParameters); + setField("sessionToolTable", workflow.fields.sessionToolTable); + setField("sessionGcode", workflow.fields.sessionGcode); + setField("sessionSnapshot", workflow.fields.sessionSnapshot); setLog( [ "Restored and loaded machine session snapshot.", - ...sessionSnapshotSummaryLogLines(summary), - ...machineSessionLoadLogLines(loadSummary), - `G-code: ${summary.gcodeOpfsPath ?? "-"}`, + ...workflow.logLines, ].join("\n"), ); } catch (error) { diff --git a/wasm-port/runtime/ui/ini-panel/session-workflow.js b/wasm-port/runtime/ui/ini-panel/session-workflow.js index da1e36f..a851393 100644 --- a/wasm-port/runtime/ui/ini-panel/session-workflow.js +++ b/wasm-port/runtime/ui/ini-panel/session-workflow.js @@ -1,3 +1,7 @@ +import { + createMachineSessionLoadSummary, + machineSessionLoadLogLines, +} from "./session-load-summary.js"; import { createSessionSnapshotSummary, sessionSnapshotSummaryLogLines, @@ -23,6 +27,16 @@ export function sessionSnapshotSaveLogLines(summary) { ]; } +export function sessionSnapshotRestoreLogLines(summary) { + return [ + ...sessionSnapshotSummaryLogLines(summary), + `INI: ${summary.iniOpfsPath}`, + `Parameter file: ${summary.parameterOpfsPath}`, + `Tool table file: ${summary.toolTableOpfsPath}`, + `G-code: ${summary.gcodeOpfsPath ?? "-"}`, + ]; +} + export async function saveMachineSessionSnapshotWorkflow({ machineId, snapshotId, @@ -63,3 +77,54 @@ export async function saveMachineSessionSnapshotWorkflow({ logLines: sessionSnapshotSaveLogLines(summary), }; } + +export async function restoreMachineSessionSnapshotWorkflow({ + machineId, + snapshotId, + snapshotFilename, + snapshotLabel, + loadMachineSessionSnapshot, + loadMachineTextFiles, + loadSession = null, +}) { + const snapshot = await loadMachineSessionSnapshot( + snapshotId, + machineId, + { filename: snapshotFilename }, + ); + const files = await loadMachineTextFiles(machineId); + const summary = createSessionSnapshotSummary(snapshot, snapshotLabel ?? snapshotLabelFromSession(snapshot)); + const fields = createSessionSnapshotFieldState(summary); + const result = { + snapshot, + editorIniText: files.ini, + summary, + fields, + loadSummary: null, + loadedSession: null, + logLines: sessionSnapshotRestoreLogLines(summary), + }; + if (loadSession) { + const loadedSession = await loadSession(); + const loadSummary = createMachineSessionLoadSummary(loadedSession); + result.loadedSession = loadedSession; + result.loadSummary = loadSummary; + result.fields = { + ...fields, + sessionIni: loadSummary.iniWasmPath, + sessionParameters: loadSummary.parameterWasmPath, + sessionToolTable: loadSummary.toolTableWasmPath, + sessionGcode: summary.gcodeOpfsPath, + }; + result.logLines = [ + ...sessionSnapshotSummaryLogLines(summary), + ...machineSessionLoadLogLines(loadSummary), + `G-code: ${summary.gcodeOpfsPath ?? "-"}`, + ]; + } + return result; +} + +function snapshotLabelFromSession(snapshot) { + return snapshot?.metadata?.snapshotLabel ?? snapshot?.sessionId ?? null; +} diff --git a/wasm-port/tests/ui/node/verify_ini_panel_session_workflow.mjs b/wasm-port/tests/ui/node/verify_ini_panel_session_workflow.mjs index 949ed87..03a2700 100644 --- a/wasm-port/tests/ui/node/verify_ini_panel_session_workflow.mjs +++ b/wasm-port/tests/ui/node/verify_ini_panel_session_workflow.mjs @@ -1,11 +1,12 @@ import assert from "node:assert/strict"; import { + restoreMachineSessionSnapshotWorkflow, saveMachineSessionSnapshotWorkflow, } from "../../../runtime/ui/ini-panel/session-workflow.js"; const calls = []; -const workflow = await saveMachineSessionSnapshotWorkflow({ +const saveWorkflow = await saveMachineSessionSnapshotWorkflow({ machineId: "xyzab-tdr", snapshotId: "ui-machine-session", snapshotFilename: "ui-machine-session.json", @@ -76,7 +77,7 @@ assert.deepEqual(calls, [ ], ]); -assert.deepEqual(workflow.summary, { +assert.deepEqual(saveWorkflow.summary, { snapshotLabel: "ui-machine-session/ui-machine-session.json", workflow: "save-session-snapshot", defaultGcodeOpfsPath: "linuxcnc/gcode/ui-session.ngc", @@ -86,7 +87,7 @@ assert.deepEqual(workflow.summary, { gcodeOpfsPath: "linuxcnc/gcode/ui-session.ngc", }); -assert.deepEqual(workflow.fields, { +assert.deepEqual(saveWorkflow.fields, { sessionIni: "linuxcnc/machines/xyzab-tdr/machine.ini", sessionParameters: "linuxcnc/machines/xyzab-tdr/linuxcnc.var", sessionToolTable: "linuxcnc/machines/xyzab-tdr/tool.tbl", @@ -94,7 +95,7 @@ assert.deepEqual(workflow.fields, { sessionSnapshot: "ui-machine-session/ui-machine-session.json", }); -assert.deepEqual(workflow.logLines, [ +assert.deepEqual(saveWorkflow.logLines, [ "Snapshot: ui-machine-session/ui-machine-session.json", "Workflow: save-session-snapshot", "Default G-code: linuxcnc/gcode/ui-session.ngc", @@ -104,4 +105,148 @@ assert.deepEqual(workflow.logLines, [ "G-code: linuxcnc/gcode/ui-session.ngc", ]); +const restoreCalls = []; +const restoreWorkflow = await restoreMachineSessionSnapshotWorkflow({ + machineId: "xyzab-tdr", + snapshotId: "ui-machine-session", + snapshotFilename: "ui-machine-session.json", + snapshotLabel: "ui-machine-session/ui-machine-session.json", + loadMachineSessionSnapshot: async (snapshotId, machineId, options) => { + restoreCalls.push(["loadMachineSessionSnapshot", snapshotId, machineId, options]); + return { + sessionId: snapshotId, + metadata: { + workflow: "save-session-snapshot", + snapshotLabel: "ui-machine-session/ui-machine-session.json", + defaultGcodeOpfsPath: "linuxcnc/gcode/ui-session.ngc", + }, + payload: { + files: { + ini: "linuxcnc/machines/xyzab-tdr/machine.ini", + parameters: "linuxcnc/machines/xyzab-tdr/linuxcnc.var", + toolTable: "linuxcnc/machines/xyzab-tdr/tool.tbl", + gcode: "linuxcnc/gcode/ui-session.ngc", + }, + }, + }; + }, + loadMachineTextFiles: async (machineId) => { + restoreCalls.push(["loadMachineTextFiles", machineId]); + return { + ini: "[EMC]\nMACHINE = xyzab-tdr\n", + }; + }, +}); + +assert.deepEqual(restoreCalls, [ + [ + "loadMachineSessionSnapshot", + "ui-machine-session", + "xyzab-tdr", + { filename: "ui-machine-session.json" }, + ], + ["loadMachineTextFiles", "xyzab-tdr"], +]); + +assert.deepEqual(restoreWorkflow.fields, { + sessionIni: "linuxcnc/machines/xyzab-tdr/machine.ini", + sessionParameters: "linuxcnc/machines/xyzab-tdr/linuxcnc.var", + sessionToolTable: "linuxcnc/machines/xyzab-tdr/tool.tbl", + sessionGcode: "linuxcnc/gcode/ui-session.ngc", + sessionSnapshot: "ui-machine-session/ui-machine-session.json", +}); + +assert.deepEqual(restoreWorkflow.logLines, [ + "Snapshot: ui-machine-session/ui-machine-session.json", + "Workflow: save-session-snapshot", + "Default G-code: linuxcnc/gcode/ui-session.ngc", + "INI: linuxcnc/machines/xyzab-tdr/machine.ini", + "Parameter file: linuxcnc/machines/xyzab-tdr/linuxcnc.var", + "Tool table file: linuxcnc/machines/xyzab-tdr/tool.tbl", + "G-code: linuxcnc/gcode/ui-session.ngc", +]); + +const restoreLoadCalls = []; +const restoreLoadWorkflow = await restoreMachineSessionSnapshotWorkflow({ + machineId: "xyzab-tdr", + snapshotId: "ui-machine-session", + snapshotFilename: "ui-machine-session.json", + snapshotLabel: "ui-machine-session/ui-machine-session.json", + loadMachineSessionSnapshot: async (snapshotId, machineId, options) => { + restoreLoadCalls.push(["loadMachineSessionSnapshot", snapshotId, machineId, options]); + return { + sessionId: snapshotId, + metadata: { + workflow: "save-session-snapshot", + snapshotLabel: "ui-machine-session/ui-machine-session.json", + defaultGcodeOpfsPath: "linuxcnc/gcode/ui-session.ngc", + }, + payload: { + files: { + ini: "linuxcnc/machines/xyzab-tdr/machine.ini", + parameters: "linuxcnc/machines/xyzab-tdr/linuxcnc.var", + toolTable: "linuxcnc/machines/xyzab-tdr/tool.tbl", + gcode: "linuxcnc/gcode/ui-session.ngc", + }, + }, + }; + }, + loadMachineTextFiles: async (machineId) => { + restoreLoadCalls.push(["loadMachineTextFiles", machineId]); + return { + ini: "[EMC]\nMACHINE = xyzab-tdr\n", + }; + }, + loadSession: async () => { + restoreLoadCalls.push(["loadSession"]); + return { + ini: { + opfsPath: "linuxcnc/machines/xyzab-tdr/machine.ini", + wasmPath: "/work/session-machine.ini", + }, + parameters: { + opfsPath: "linuxcnc/machines/xyzab-tdr/linuxcnc.var", + wasmPath: "/work/session-linuxcnc.var", + result: "restore_parameters=0", + }, + toolTable: { + opfsPath: "linuxcnc/machines/xyzab-tdr/tool.tbl", + wasmPath: "/work/session-tool.tbl", + result: "tooldata_load=0", + }, + }; + }, +}); + +assert.deepEqual(restoreLoadCalls, [ + [ + "loadMachineSessionSnapshot", + "ui-machine-session", + "xyzab-tdr", + { filename: "ui-machine-session.json" }, + ], + ["loadMachineTextFiles", "xyzab-tdr"], + ["loadSession"], +]); + +assert.deepEqual(restoreLoadWorkflow.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", +}); + +assert.deepEqual(restoreLoadWorkflow.logLines, [ + "Snapshot: ui-machine-session/ui-machine-session.json", + "Workflow: save-session-snapshot", + "Default G-code: linuxcnc/gcode/ui-session.ngc", + "INI: linuxcnc/machines/xyzab-tdr/machine.ini -> /work/session-machine.ini", + "Parameter file: linuxcnc/machines/xyzab-tdr/linuxcnc.var -> /work/session-linuxcnc.var", + "Parameters: restore_parameters=0", + "Tool table file: linuxcnc/machines/xyzab-tdr/tool.tbl -> /work/session-tool.tbl", + "Tool table: tooldata_load=0", + "G-code: linuxcnc/gcode/ui-session.ngc", +]); + console.log("ini_panel_session_workflow_node_smoke=ok");