diff --git a/text10.txt b/text10.txt index 335a7d7..4a8d7aa 100644 --- a/text10.txt +++ b/text10.txt @@ -230,6 +230,60 @@ groups,重点检查 `saveMachineTextFiles(...)` 相关输入是否已有可复 - 把 save API 输入对象提成无用常量; - 触碰 runtime、SDK、OPFS bridge 或 LinuxCNC-owned semantics。 +二、2026-06-14 继续执行记录:browser OPFS session snapshot manifest helper + +本轮按“暂停性能测试,进行实质性功能推进”的要求,补充了一个新的 OPFS/runtime +能力:生成可恢复的机器会话快照清单,范围仍局限在 `runtime/opfs` 与对应 smoke。 + +完成内容: + +- 在 `wasm-port/runtime/opfs/snapshot-store.js` 新增 + `createMachineSessionSnapshotPayload(machineId, options)`; +- 该 helper 基于现有 `path-model` 生成机器级 session snapshot payload,默认包含 + `ini`、`parameters`、`toolTable`,可选包含 `gcode`; +- 支持自定义 `iniOpfsPath`、`parameterOpfsPath`、`toolTableOpfsPath`、`gcodeOpfsPath`; +- 支持自定义 `iniFilename`、`parameterFilename`、`toolTableFilename`、`gcodeFilename`; +- 在 Node OPFS smoke 与 browser INI panel smoke 中补了覆盖,验证默认路径、自定义 + 文件名和 G-code 路径均能按预期生成; +- 未改变 session snapshot 的格式、版本、存储位置或既有 save/load 行为; +- 未改变 OPFS bridge behavior、file-service、SDK planner、interpreter、INI、tool、 + parameter 或 LinuxCNC-owned runtime semantics。 + +验证已通过: + +```bash +git diff --check +wasm-port/tests/opfs/node/verify_file_service.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 +opfs_file_service_node_smoke=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 +browser_interp_smoke=ok +host_wasm_opfs_browser_smokes=ok +``` + +下一步建议: + +继续把这个 helper 扩到更明确的实际入口,优先扫描 browser/Node 里已有的 session +snapshot 调用是否能复用它,或者把 machine session 生成入口整理到更显式的 API; +只做 OPFS/filesystem/browser boundary 相关功能推进,不碰 CNC 语义。 + 一、2026-06-14 继续执行记录:browser explicit readback path groups guard 本轮按 `text10.txt` 第一候选批次继续收敛 explicit session saved readback path groups, diff --git a/wasm-port/runtime/opfs/snapshot-store.js b/wasm-port/runtime/opfs/snapshot-store.js index 5d60d27..f1f7246 100644 --- a/wasm-port/runtime/opfs/snapshot-store.js +++ b/wasm-port/runtime/opfs/snapshot-store.js @@ -1,5 +1,12 @@ import { loadTextFile, saveTextFile } from "./file-service.js"; -import { sessionSnapshotPath } from "./path-model.js"; +import { + gcodeProgramPath, + machineIniPath, + normalizeOpfsPath, + parameterFilePath, + sessionSnapshotPath, + toolTablePath, +} from "./path-model.js"; export const SESSION_SNAPSHOT_FORMAT = "linuxcnc-wasm-session-snapshot"; export const SESSION_SNAPSHOT_VERSION = 1; @@ -10,6 +17,38 @@ function assertPlainObject(value, label) { } } +function optionalOpfsPath(value, label) { + if (value === undefined) { + return undefined; + } + if (typeof value !== "string") { + throw new Error(`${label} must be a string.`); + } + return normalizeOpfsPath(value); +} + +export function createMachineSessionSnapshotPayload(machineId, options = {}) { + assertPlainObject(options, "machine session snapshot options"); + const ini = optionalOpfsPath(options.iniOpfsPath, "INI OPFS path") ?? + machineIniPath(machineId, options.iniFilename); + const parameters = optionalOpfsPath(options.parameterOpfsPath, "parameter OPFS path") ?? + parameterFilePath(machineId, options.parameterFilename); + const toolTable = optionalOpfsPath(options.toolTableOpfsPath, "tool table OPFS path") ?? + toolTablePath(machineId, options.toolTableFilename); + const gcode = optionalOpfsPath(options.gcodeOpfsPath, "G-code OPFS path") ?? + (options.gcodeFilename === undefined ? undefined : gcodeProgramPath(options.gcodeFilename)); + + return { + machineId, + files: { + ini, + parameters, + toolTable, + ...(gcode === undefined ? {} : { gcode }), + }, + }; +} + export function createSessionSnapshot(sessionId, payload, options = {}) { sessionSnapshotPath(sessionId, options.filename); assertPlainObject(payload, "snapshot payload"); diff --git a/wasm-port/tests/browser/ini_panel_smoke.html b/wasm-port/tests/browser/ini_panel_smoke.html index 17a314c..aa300e0 100644 --- a/wasm-port/tests/browser/ini_panel_smoke.html +++ b/wasm-port/tests/browser/ini_panel_smoke.html @@ -15,6 +15,7 @@ sessionSnapshotPath, } from "../../runtime/opfs/path-model.js"; import { + createMachineSessionSnapshotPayload, loadSessionSnapshot, saveSessionSnapshot, } from "../../runtime/opfs/snapshot-store.js"; @@ -124,7 +125,26 @@ TOOL_TABLE = browser-tool.tbl await saveTextFile(opfsPath, iniText); assertEqual(await loadTextFile(opfsPath), iniText, "opfs roundtrip"); - const snapshotPayload = { files: { ini: opfsPath } }; + const snapshotPayload = createMachineSessionSnapshotPayload("browser-smoke", { + gcodeFilename: "browser-smoke.ngc", + }); + assertEqual(snapshotPayload.machineId, "browser-smoke", "snapshot machine"); + assertEqual(snapshotPayload.files.ini, opfsPath, "snapshot payload INI"); + assertEqual( + snapshotPayload.files.parameters, + "linuxcnc/machines/browser-smoke/linuxcnc.var", + "snapshot payload parameter file", + ); + assertEqual( + snapshotPayload.files.toolTable, + "linuxcnc/machines/browser-smoke/tool.tbl", + "snapshot payload tool table", + ); + assertEqual( + snapshotPayload.files.gcode, + "linuxcnc/gcode/browser-smoke.ngc", + "snapshot payload G-code", + ); const savedSnapshot = await saveSessionSnapshot( "browser-session", snapshotPayload, @@ -139,7 +159,28 @@ TOOL_TABLE = browser-tool.tbl assertEqual(loadedSnapshot.sessionId, "browser-session", "snapshot session"); assertEqual(loadedSnapshot.createdAt, savedSnapshot.createdAt, "snapshot timestamp"); assertEqual(loadedSnapshot.payload.files.ini, opfsPath, "snapshot payload"); + assertEqual(loadedSnapshot.payload.files.gcode, snapshotPayload.files.gcode, "snapshot G-code payload"); assertEqual(loadedSnapshot.metadata.source, "browser-smoke", "snapshot metadata"); + const customMachineSnapshotPayload = createMachineSessionSnapshotPayload("browser-smoke", { + parameterFilename: "browser-linuxcnc.var", + toolTableFilename: "browser-tool.tbl", + gcodeOpfsPath: "linuxcnc/gcode/browser-custom.ngc", + }); + assertEqual( + customMachineSnapshotPayload.files.parameters, + "linuxcnc/machines/browser-smoke/browser-linuxcnc.var", + "custom snapshot parameter file", + ); + assertEqual( + customMachineSnapshotPayload.files.toolTable, + "linuxcnc/machines/browser-smoke/browser-tool.tbl", + "custom snapshot tool table", + ); + assertEqual( + customMachineSnapshotPayload.files.gcode, + "linuxcnc/gcode/browser-custom.ngc", + "custom snapshot G-code", + ); assertEqual( sessionSnapshotPath("browser-session", "browser-custom-snapshot.json"), "linuxcnc/sessions/browser-session/browser-custom-snapshot.json", diff --git a/wasm-port/tests/opfs/node/verify_file_service.mjs b/wasm-port/tests/opfs/node/verify_file_service.mjs index eeaa7b9..0db1784 100644 --- a/wasm-port/tests/opfs/node/verify_file_service.mjs +++ b/wasm-port/tests/opfs/node/verify_file_service.mjs @@ -16,6 +16,7 @@ import { toolTablePath, } from "../../../runtime/opfs/path-model.js"; import { + createMachineSessionSnapshotPayload, createSessionSnapshot, loadSessionSnapshot, saveSessionSnapshot, @@ -216,12 +217,38 @@ assertSessionSnapshotStoragePath( assertMachineFileStoragePaths("xyzab-tdr", defaultMachinePaths("xyzab-tdr")); assertMachineFileStoragePaths("xyzab-tdr", machineFilePaths("xyzab-tdr")); -const snapshotPayload = { +const snapshotPayload = createMachineSessionSnapshotPayload("xyzab-tdr", { + gcodeFilename: "fixture.ngc", +}); +assert.deepEqual(snapshotPayload, { + machineId: "xyzab-tdr", files: { ini: machineIniPath("xyzab-tdr"), + parameters: parameterFilePath("xyzab-tdr"), toolTable: toolTablePath("xyzab-tdr"), + gcode: gcodeProgramPath("fixture.ngc"), }, -}; +}); +assert.deepEqual( + createMachineSessionSnapshotPayload("custom-machine", { + parameterFilename: "custom.var", + toolTableFilename: "custom-tool.tbl", + gcodeOpfsPath: "linuxcnc/gcode/custom-fixture.ngc", + }), + { + machineId: "custom-machine", + files: { + ini: "linuxcnc/machines/custom-machine/machine.ini", + parameters: "linuxcnc/machines/custom-machine/custom.var", + toolTable: "linuxcnc/machines/custom-machine/custom-tool.tbl", + gcode: "linuxcnc/gcode/custom-fixture.ngc", + }, + }, +); +assert.throws( + () => createMachineSessionSnapshotPayload("bad-machine", { gcodeFilename: "../escape.ngc" }), + /Invalid G-code filename/, +); const snapshot = createSessionSnapshot("session-1", snapshotPayload, { createdAt: "2026-06-08T00:00:00.000Z", metadata: { source: "node-smoke" },