diff --git a/wasm-port/docs/compatibility-validation.md b/wasm-port/docs/compatibility-validation.md index c7c4819..a728be5 100644 --- a/wasm-port/docs/compatibility-validation.md +++ b/wasm-port/docs/compatibility-validation.md @@ -100,9 +100,9 @@ paths, unavailable OPFS storage, and the host-side OPFS path model for INI, tool table, parameter, G-code, preview-cache, and session-snapshot storage targets. It also validates the host-side session snapshot JSON envelope and round-trip store, including unsupported format/version, session-id mismatch, -and non-object metadata/payload rejection, plus pure-text machine file and -G-code stores without defining CNC machine-state or file-format semantics. It -now also validates the +non-object metadata/payload rejection, custom snapshot filenames, and invalid +snapshot filename rejection, plus pure-text machine file and G-code stores +without defining CNC machine-state or file-format semantics. It now also validates the OPFS-to-WASM parameter-file bridge with a mock interpreter SDK to ensure the host boundary copies text into and out of the WASM filesystem without defining parameter semantics, and that INI-derived machine file names are still @@ -114,8 +114,8 @@ keeping host override policy outside CNC semantics. The browser INI/OPFS smoke script serves `wasm-port/` over localhost and runs Chromium headless against a test page that imports the JS SDK, loads the INI WASM module, queries vendored LinuxCNC INI parsing through the SDK, and -performs an OPFS text-file, generic session snapshot, invalid snapshot -envelope, machine file, and G-code text round trip. +performs an OPFS text-file, generic session snapshot, custom snapshot filename, +invalid snapshot filename/envelope, machine file, and G-code text round trip. The browser interpreter smoke script serves `wasm-port/` over localhost and runs Chromium headless against a test page that loads the interpreter-core @@ -186,8 +186,8 @@ The validation fails if: | --- | --- | | `tests/wasm/node/verify_ini_wasm.sh` | Validates the browser-facing INI WASM module can be built from vendored LinuxCNC `inifile.cc`, loaded through the JS SDK in Node, and queried through the exported C ABI. | | `tests/wasm/node/verify_interp_wasm.sh` | Validates the initial interpreter-core WASM module can be built from vendored LinuxCNC interpreter source, loaded through the interpreter JS SDK, run the first fixture group through `Interp::execute()` and selected file fixtures through `Interp::open()`/`read()`/`execute()`, match the native canonical event plus required state readback fixtures, and run parameter-file restore/save through vendored LinuxCNC `Interp::restore_parameters()` and `Interp::save_parameters()`. | -| `tests/opfs/node/verify_file_service.sh` | Validates the host-owned OPFS text-file adapter, path model, session snapshot store including envelope rejection paths, machine file store, G-code text store, OPFS-to-WASM parameter/tool-table bridges, and grouped machine-session loading without moving file persistence, parameter semantics, or tool-table semantics into the WASM core. | -| `tests/browser/verify_ini_panel_browser.sh` | Validates the INI SDK, INI/interpreter WASM module loading, OPFS text-file round trip, generic session snapshot round trip plus envelope rejection paths, machine file text round trip, G-code text round trip, and the INI panel UI's machine-session load, G-code run, and canonical-event display paths in a real browser runtime. | +| `tests/opfs/node/verify_file_service.sh` | Validates the host-owned OPFS text-file adapter, path model, session snapshot store including custom filenames and envelope/path rejection paths, machine file store, G-code text store, OPFS-to-WASM parameter/tool-table bridges, and grouped machine-session loading without moving file persistence, parameter semantics, or tool-table semantics into the WASM core. | +| `tests/browser/verify_ini_panel_browser.sh` | Validates the INI SDK, INI/interpreter WASM module loading, OPFS text-file round trip, generic session snapshot round trip plus custom filename and envelope/path rejection paths, machine file text round trip, G-code text round trip, and the INI panel UI's machine-session load, G-code run, and canonical-event display paths in a real browser runtime. | | `tests/browser/verify_interp_browser.sh` | Validates the interpreter-core WASM module loads through the interpreter JS SDK in a real browser runtime and runs selected positive and negative canonical fixtures through vendored LinuxCNC `Interp::execute()` plus `Interp::open()`/`read()`/`execute()` via the exported C ABI, including OPFS-backed parameter-file restore/save and tool-table load/save through vendored LinuxCNC source. | | `tests/host/verify_host_smokes.sh` | Runs the current host-side Node, WASM interpreter-core, OPFS, and browser smoke validation with shared WASM builds. | @@ -270,10 +270,11 @@ derived from vendored LinuxCNC INI boolean parsing, INI-derived `[RS274NGC]PARAMETER_FILE` and `[EMCIO]TOOL_TABLE` file names mapped to OPFS machine files, OPFS path-model rejection of invalid INI-derived file names, explicit host session file-name overrides taking precedence over INI-derived -names, session snapshot envelope rejection for unsupported format/version and -wrong session id, and a browser interpreter smoke that uses the same session -bridge with real LinuxCNC INI WASM parsing before saving OPFS-backed parameter -and tool-table text through vendored LinuxCNC file APIs. +names, session snapshot custom filename handling plus envelope/path rejection +for unsupported format/version, wrong session id, and invalid snapshot +filenames, and a browser interpreter smoke that uses the same session bridge +with real LinuxCNC INI WASM parsing before saving OPFS-backed parameter and +tool-table text through vendored LinuxCNC file APIs. Full browser coverage, full SDK coverage, and richer machine-state validation remain future work. diff --git a/wasm-port/docs/drift-report.md b/wasm-port/docs/drift-report.md index a677a8f..6e782d4 100644 --- a/wasm-port/docs/drift-report.md +++ b/wasm-port/docs/drift-report.md @@ -71,8 +71,8 @@ semantic rewrites: file and G-code storage, parameter/tool-table bridge copying into the interpreter SDK filesystem, grouped machine-session loading, explicit session file-name overrides, invalid INI-derived machine file names, generic - session snapshot envelope rejection paths, and a Chromium localhost round - trip for those persistence paths. + session snapshot custom filenames plus envelope/path rejection paths, and a + Chromium localhost round trip for those persistence paths. - Host-side smoke validation is aggregated by `tests/host/verify_host_smokes.sh` so Node, WASM, OPFS, and browser checks run from one command. diff --git a/wasm-port/docs/source-reuse-map.md b/wasm-port/docs/source-reuse-map.md index 09f5cd0..cd693cf 100644 --- a/wasm-port/docs/source-reuse-map.md +++ b/wasm-port/docs/source-reuse-map.md @@ -83,8 +83,9 @@ Current validation is intentionally mechanical: `runtime/opfs/file-service.js` adapter. `runtime/opfs/path-model.js` now defines paths for INI, tool table, parameter file, G-code program, preview-cache, and session-snapshot targets. `runtime/opfs/snapshot-store.js` - adds a generic JSON session snapshot envelope and validates its format, - version, session id, metadata, and payload shape. + adds a generic JSON session snapshot envelope, supports custom snapshot + filenames under the session directory, and validates its format, version, + session id, metadata, payload shape, and filename boundary. `runtime/opfs/machine-file-store.js` adds pure-text storage for INI, tool table, parameter file, and G-code program content. `runtime/opfs/linuxcnc-parameter-bridge.js` copies OPFS-backed parameter diff --git a/wasm-port/tests/browser/ini_panel_smoke.html b/wasm-port/tests/browser/ini_panel_smoke.html index 46dded0..4f6b9d9 100644 --- a/wasm-port/tests/browser/ini_panel_smoke.html +++ b/wasm-port/tests/browser/ini_panel_smoke.html @@ -116,6 +116,43 @@ JOINTS = 3 assertEqual(loadedSnapshot.createdAt, savedSnapshot.createdAt, "snapshot timestamp"); assertEqual(loadedSnapshot.payload.files.ini, opfsPath, "snapshot payload"); assertEqual(loadedSnapshot.metadata.source, "browser-smoke", "snapshot metadata"); + assertEqual( + sessionSnapshotPath("browser-session", "browser-custom-snapshot.json"), + "linuxcnc/sessions/browser-session/browser-custom-snapshot.json", + "snapshot custom path", + ); + const customSnapshot = await saveSessionSnapshot( + "browser-session", + snapshotPayload, + { + filename: "browser-custom-snapshot.json", + createdAt: "2026-06-08T00:00:00.000Z", + metadata: { source: "browser-smoke-custom" }, + }, + ); + const loadedCustomSnapshot = await loadSessionSnapshot( + "browser-session", + { filename: "browser-custom-snapshot.json" }, + ); + assertEqual( + loadedCustomSnapshot.metadata.source, + customSnapshot.metadata.source, + "custom snapshot metadata", + ); + await assertRejects( + "browser snapshot invalid filename save", + () => saveSessionSnapshot("browser-session", {}, { + filename: "nested/snapshot.json", + }), + /Invalid session snapshot filename/, + ); + await assertRejects( + "browser snapshot invalid filename load", + () => loadSessionSnapshot("browser-session", { + filename: "../snapshot.json", + }), + /Invalid session snapshot filename/, + ); await saveTextFile( sessionSnapshotPath("browser-bad-format"), JSON.stringify({ diff --git a/wasm-port/tests/opfs/node/verify_file_service.mjs b/wasm-port/tests/opfs/node/verify_file_service.mjs index abfd2d8..4d8ce4d 100644 --- a/wasm-port/tests/opfs/node/verify_file_service.mjs +++ b/wasm-port/tests/opfs/node/verify_file_service.mjs @@ -104,6 +104,10 @@ assert.equal(parameterFilePath("xyzab-tdr"), "linuxcnc/machines/xyzab-tdr/linuxc assert.equal(gcodeProgramPath("fixture.ngc"), "linuxcnc/gcode/fixture.ngc"); assert.equal(previewCachePath("fixture"), "linuxcnc/preview-cache/fixture/preview.json"); assert.equal(sessionSnapshotPath("session-1"), "linuxcnc/sessions/session-1/snapshot.json"); +assert.equal( + sessionSnapshotPath("session-1", "custom-snapshot.json"), + "linuxcnc/sessions/session-1/custom-snapshot.json", +); assert.deepEqual(defaultMachinePaths("xyzab-tdr"), { ini: "linuxcnc/machines/xyzab-tdr/machine.ini", toolTable: "linuxcnc/machines/xyzab-tdr/tool.tbl", @@ -167,6 +171,23 @@ await saveSessionSnapshot("session-1", snapshotPayload, { metadata: { source: "node-smoke" }, }); assert.deepEqual(await loadSessionSnapshot("session-1", { storage }), snapshot); +const customSnapshot = await saveSessionSnapshot("session-1", snapshotPayload, { + storage, + filename: "custom-snapshot.json", + createdAt: "2026-06-08T00:00:00.000Z", + metadata: { source: "node-smoke-custom" }, +}); +assert.equal( + customSnapshot.metadata.source, + "node-smoke-custom", +); +assert.deepEqual( + await loadSessionSnapshot("session-1", { + storage, + filename: "custom-snapshot.json", + }), + customSnapshot, +); await saveMachineTextFiles("xyzab-tdr", { ini: "[EMC]\nMACHINE = xyzab-tdr\n", @@ -569,6 +590,24 @@ assert.throws( () => machineIniPath("../escape"), /Invalid machine id/, ); +assert.throws( + () => sessionSnapshotPath("session-1", "nested/snapshot.json"), + /Invalid session snapshot filename/, +); +await assert.rejects( + () => saveSessionSnapshot("session-1", {}, { + storage, + filename: "nested/snapshot.json", + }), + /Invalid session snapshot filename/, +); +await assert.rejects( + () => loadSessionSnapshot("session-1", { + storage, + filename: "../snapshot.json", + }), + /Invalid session snapshot filename/, +); await assert.rejects( () => saveMachineTextFiles("xyzab-tdr", null, { storage }), /machine files must be a plain object/,