推进 INI 面板 session snapshot workflow helper

This commit is contained in:
2026-06-14 21:21:17 +08:00
parent 48a0f61b4a
commit 4e4a9c667f
7 changed files with 356 additions and 71 deletions

View File

@@ -8,6 +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.
The helpers do not implement CNC behavior. G-code execution, canonical events,
tool handling, parameters, kinematics, and planner behavior remain owned by the
@@ -53,6 +56,42 @@ The returned summary has this shape:
}
```
## `saveMachineSessionSnapshotWorkflow(input)`
The workflow writes the current INI/tool/parameter text files, writes the
default G-code program, saves the machine session snapshot, and returns the
snapshot summary, field state, and log lines for UI consumers.
The persistence functions are injected by the caller:
```js
{
saveMachineTextFiles,
saveGcodeProgram,
saveMachineSessionSnapshot
}
```
The returned object has this shape:
```js
{
snapshot: { ... },
summary: { snapshotLabel: "...", workflow: "save-session-snapshot" },
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"
},
logLines: [
"Snapshot: ui-machine-session/ui-machine-session.json",
"Workflow: save-session-snapshot"
]
}
```
## Log Helpers
- `sessionSnapshotSummaryLogLines(summary)` renders snapshot label, workflow,
@@ -61,6 +100,8 @@ The returned summary has this shape:
save/restore log summaries.
- `machineSessionLoadLogLines(summary)` renders the interpreter/session
OPFS-to-WASM mapping and captured result lines.
- `sessionSnapshotSaveLogLines(summary)` renders the save-workflow snapshot
paths used by the INI panel.
## Boundary Rules
@@ -76,6 +117,7 @@ Run the focused Node smoke after changing these helpers:
```bash
wasm-port/tests/ui/node/verify_ini_panel_session_summary.sh
wasm-port/tests/ui/node/verify_ini_panel_session_workflow.sh
```
The aggregate UI Node smoke is:

View File

@@ -41,6 +41,9 @@ import {
sessionSnapshotMetadataLogLines,
sessionSnapshotSummaryLogLines,
} from "./session-summary.js";
import {
saveMachineSessionSnapshotWorkflow,
} from "./session-workflow.js";
const SAMPLE_PATH =
"../../../vendor/linuxcnc/configs/sim/axis/vismach/5axis/table-dual-rotary/xyzab-tdr.ini";
@@ -566,35 +569,28 @@ document.getElementById("save-machine-files").addEventListener("click", async ()
document.getElementById("save-session-snapshot").addEventListener("click", async () => {
try {
await saveMachineTextFiles(MACHINE_ID, {
ini: editor.value,
toolTable: DEFAULT_TOOL_TABLE,
parameters: DEFAULT_PARAMETERS,
const workflow = await saveMachineSessionSnapshotWorkflow({
machineId: MACHINE_ID,
snapshotId: UI_SESSION.snapshotId,
snapshotFilename: UI_SESSION.snapshotFilename,
snapshotLabel: sessionSnapshotLabel(UI_SESSION),
iniText: editor.value,
toolTableText: DEFAULT_TOOL_TABLE,
parameterText: DEFAULT_PARAMETERS,
gcodeFilename: UI_SESSION.defaultGcodeFilename,
gcodeText: DEFAULT_GCODE,
metadata: sessionSnapshotMetadata(),
saveMachineTextFiles,
saveGcodeProgram,
saveMachineSessionSnapshot,
});
await saveGcodeProgram(UI_SESSION.defaultGcodeFilename, DEFAULT_GCODE);
const snapshot = await saveMachineSessionSnapshot(
UI_SESSION.snapshotId,
MACHINE_ID,
{
filename: UI_SESSION.snapshotFilename,
gcodeFilename: UI_SESSION.defaultGcodeFilename,
metadata: sessionSnapshotMetadata(),
},
);
setField("sessionIni", snapshot.payload.files.ini);
setField("sessionParameters", snapshot.payload.files.parameters);
setField("sessionToolTable", snapshot.payload.files.toolTable);
setField("sessionGcode", snapshot.payload.files.gcode);
setField("sessionSnapshot", sessionSnapshotLabel(UI_SESSION));
const summary = createSessionSnapshotSummary(snapshot, sessionSnapshotLabel(UI_SESSION));
for (const [name, value] of Object.entries(workflow.fields)) {
setField(name, value);
}
setLog(
[
"Saved machine session snapshot to 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) {

View File

@@ -0,0 +1,65 @@
import {
createSessionSnapshotSummary,
sessionSnapshotSummaryLogLines,
} from "./session-summary.js";
export function createSessionSnapshotFieldState(summary) {
return {
sessionIni: summary.iniOpfsPath,
sessionParameters: summary.parameterOpfsPath,
sessionToolTable: summary.toolTableOpfsPath,
sessionGcode: summary.gcodeOpfsPath,
sessionSnapshot: summary.snapshotLabel,
};
}
export function sessionSnapshotSaveLogLines(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,
snapshotFilename,
snapshotLabel,
iniText,
toolTableText,
parameterText,
gcodeFilename,
gcodeText,
metadata,
saveMachineTextFiles,
saveGcodeProgram,
saveMachineSessionSnapshot,
}) {
await Promise.all([
saveMachineTextFiles(machineId, {
ini: iniText,
toolTable: toolTableText,
parameters: parameterText,
}),
saveGcodeProgram(gcodeFilename, gcodeText),
]);
const snapshot = await saveMachineSessionSnapshot(
snapshotId,
machineId,
{
filename: snapshotFilename,
gcodeFilename,
metadata,
},
);
const summary = createSessionSnapshotSummary(snapshot, snapshotLabel);
return {
snapshot,
summary,
fields: createSessionSnapshotFieldState(summary),
logLines: sessionSnapshotSaveLogLines(summary),
};
}

View File

@@ -0,0 +1,107 @@
import assert from "node:assert/strict";
import {
saveMachineSessionSnapshotWorkflow,
} from "../../../runtime/ui/ini-panel/session-workflow.js";
const calls = [];
const workflow = await saveMachineSessionSnapshotWorkflow({
machineId: "xyzab-tdr",
snapshotId: "ui-machine-session",
snapshotFilename: "ui-machine-session.json",
snapshotLabel: "ui-machine-session/ui-machine-session.json",
iniText: "[EMC]\nMACHINE = xyzab-tdr\n",
toolTableText: "T2 P7 Z3.125 D1.5 I12 J34 Q4 ;ui session tool\n",
parameterText: "5161 0.0\n",
gcodeFilename: "ui-session.ngc",
gcodeText: "G0 X1.0 Y2.0 (Comment)\nG1 X3.0 Y4.0 F120.0\n",
metadata: {
source: "ini-panel",
workflow: "save-session-snapshot",
snapshotLabel: "ui-machine-session/ui-machine-session.json",
defaultGcodeOpfsPath: "linuxcnc/gcode/ui-session.ngc",
},
saveMachineTextFiles: async (machineId, files) => {
calls.push(["saveMachineTextFiles", machineId, files]);
},
saveGcodeProgram: async (filename, text) => {
calls.push(["saveGcodeProgram", filename, text]);
},
saveMachineSessionSnapshot: async (snapshotId, machineId, options) => {
calls.push(["saveMachineSessionSnapshot", snapshotId, machineId, options]);
return {
sessionId: snapshotId,
metadata: options.metadata,
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",
},
},
};
},
});
assert.deepEqual(calls, [
[
"saveMachineTextFiles",
"xyzab-tdr",
{
ini: "[EMC]\nMACHINE = xyzab-tdr\n",
toolTable: "T2 P7 Z3.125 D1.5 I12 J34 Q4 ;ui session tool\n",
parameters: "5161 0.0\n",
},
],
[
"saveGcodeProgram",
"ui-session.ngc",
"G0 X1.0 Y2.0 (Comment)\nG1 X3.0 Y4.0 F120.0\n",
],
[
"saveMachineSessionSnapshot",
"ui-machine-session",
"xyzab-tdr",
{
filename: "ui-machine-session.json",
gcodeFilename: "ui-session.ngc",
metadata: {
source: "ini-panel",
workflow: "save-session-snapshot",
snapshotLabel: "ui-machine-session/ui-machine-session.json",
defaultGcodeOpfsPath: "linuxcnc/gcode/ui-session.ngc",
},
},
],
]);
assert.deepEqual(workflow.summary, {
snapshotLabel: "ui-machine-session/ui-machine-session.json",
workflow: "save-session-snapshot",
defaultGcodeOpfsPath: "linuxcnc/gcode/ui-session.ngc",
iniOpfsPath: "linuxcnc/machines/xyzab-tdr/machine.ini",
parameterOpfsPath: "linuxcnc/machines/xyzab-tdr/linuxcnc.var",
toolTableOpfsPath: "linuxcnc/machines/xyzab-tdr/tool.tbl",
gcodeOpfsPath: "linuxcnc/gcode/ui-session.ngc",
});
assert.deepEqual(workflow.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(workflow.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",
]);
console.log("ini_panel_session_workflow_node_smoke=ok");

View File

@@ -0,0 +1,6 @@
#!/usr/bin/env bash
set -euo pipefail
ROOT_DIR="$(cd "$(dirname "$0")/../../.." && pwd)"
node "$ROOT_DIR/tests/ui/node/verify_ini_panel_session_workflow.mjs"

View File

@@ -5,6 +5,7 @@ ROOT_DIR="$(cd "$(dirname "$0")/../../.." && pwd)"
"$ROOT_DIR/tests/ui/node/verify_ini_panel_run_summary.sh"
"$ROOT_DIR/tests/ui/node/verify_ini_panel_session_summary.sh"
"$ROOT_DIR/tests/ui/node/verify_ini_panel_session_workflow.sh"
"$ROOT_DIR/tests/ui/node/verify_ini_panel_machine_file_summary.sh"
"$ROOT_DIR/tests/ui/node/verify_ini_panel_state_summary.sh"