推进 INI 面板 run readiness workflow helper

This commit is contained in:
2026-06-15 05:21:01 +08:00
parent a883af2d3e
commit 4a307f63f6
8 changed files with 322 additions and 11 deletions

View File

@@ -371,6 +371,87 @@ host_wasm_opfs_browser_smokes=ok
`loadMachineSessionIntoWasmWorkflow()`,避免 restore+load 与单独 load 的 field/log 状态模型 `loadMachineSessionIntoWasmWorkflow()`,避免 restore+load 与单独 load 的 field/log 状态模型
分叉;仍保持由 LinuxCNC/OPFS bridge 执行实际语义,不在 JS 中解释 CNC 行为。 分叉;仍保持由 LinuxCNC/OPFS bridge 执行实际语义,不在 JS 中解释 CNC 行为。
三、2026-06-15 继续执行记录INI panel run readiness workflow helper
本轮按“第一原则:加快实质性推进”继续推进 run 前置状态模型,将 run 按钮可执行条件、
默认/快照 G-code 选择结果与已加载 session 的 WASM 绑定关系抽成可复用 readiness helper
让 app、state summary、control view 和 Node smoke 共享同一份运行前状态。
完成内容:
- 新增 `wasm-port/runtime/ui/ini-panel/run-workflow.js` 的
`createRunSessionReadinessState(input)`
- `runGcodeSessionWorkflow()` 现在先生成 readiness state再复用其中的选定程序和 loaded
session 信息执行实际 run
- readiness state 固定返回:
- `phase`
- `canRun`
- `missing`
- `loadedSession`
- `snapshot`
- `defaultProgram`
- `program`
- `runButton`
- `wasm-port/runtime/ui/ini-panel/app.js` 在点击 Run 前先写入 readiness state并把
`selectedProgram`、`runReadiness` 和 field state 一起更新到 panel state
- `wasm-port/runtime/ui/ini-panel/panel-state-summary.js` 新增 `runReadiness` 的 report /
workflowStatus / control-view 映射;
- `createMachineSessionControlView()` 的 Run 区新增 `Ready` 与 `Button` 行,用于 read-only
control page 读取首帧 run 前置状态;
- `wasm-port/tests/ui/node/verify_ini_panel_run_workflow.mjs` 覆盖 readiness helper 与
workflow readiness 返回值;
- `wasm-port/tests/ui/node/verify_ini_panel_state_summary.mjs` 覆盖 runReadiness 在 report、
bundle、workflowStatus 和 control view 中的映射;
- `wasm-port/docs/ui-run-summary-helpers.md` 和 `wasm-port/docs/ui-panel-state-summary.md`
同步说明 readiness helper 与 runReadiness 状态;
- 未新增控制按钮;
- 未解析 G-code
- 未解释 canonical events
- 未改变 OPFS/session persistence、LinuxCNC interpreter、tool、parameter、planner 或
LinuxCNC-owned runtime semantics。
验证已通过:
```bash
git diff --check
wasm-port/tests/ui/node/verify_ini_panel_run_workflow.sh
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_run_workflow_node_smoke=ok
ini_panel_state_summary_node_smoke=ok
ui_node_smokes=ok
browser_ini_opfs_smoke=ok
browser_ini_control_page_smoke=ok
browser_ini_launch_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
```
下一步建议:
继续推进 OPFS/session persistence 的实质链路。下一批建议将 `load-session` 与
`restore-load-session` 的 field/log 路径再做一次轻量整合,优先把“已加载 session、快照恢复、
run readiness、run 结果”汇总成一个更上层的只读工作流状态对象;仍保持只描述 UI/host
boundary facts不在 JS 中解释 G-code、canonical events 或 CNC 运行语义。
三、2026-06-15 继续执行记录INI panel restore-load workflow 复用 load-session helper 三、2026-06-15 继续执行记录INI panel restore-load workflow 复用 load-session helper
本轮按“第一原则:加快实质性推进”继续推进 OPFS/session persistence 主链路,将 本轮按“第一原则:加快实质性推进”继续推进 OPFS/session persistence 主链路,将

View File

@@ -49,6 +49,7 @@ For the short page/entry map, see `docs/panel-entry.md`.
machineFiles: { iniOpfsPath: "...", toolTableOpfsPath: "...", parameterOpfsPath: "...", gcodeOpfsPath: "..." }, machineFiles: { iniOpfsPath: "...", toolTableOpfsPath: "...", parameterOpfsPath: "...", gcodeOpfsPath: "..." },
sessionSnapshot: { snapshotLabel: "...", workflow: "save-session-snapshot" }, sessionSnapshot: { snapshotLabel: "...", workflow: "save-session-snapshot" },
sessionLoad: { iniOpfsPath: "...", iniWasmPath: "..." }, sessionLoad: { iniOpfsPath: "...", iniWasmPath: "..." },
runReadiness: { phase: "ready", canRun: true, runButton: { enabled: true } },
run: { runStatus: "ok", programSource: "snapshot" }, run: { runStatus: "ok", programSource: "snapshot" },
fiveaxisRemap: "ok" fiveaxisRemap: "ok"
} }
@@ -100,6 +101,7 @@ with four sections:
session: { snapshotLabel: "...", workflow: "...", ini: "...", parameters: "...", toolTable: "...", gcode: "..." }, session: { snapshotLabel: "...", workflow: "...", ini: "...", parameters: "...", toolTable: "...", gcode: "..." },
loaded: { iniWasmPath: "...", parameterWasmPath: "...", toolTableWasmPath: "..." }, loaded: { iniWasmPath: "...", parameterWasmPath: "...", toolTableWasmPath: "..." },
program: { source: "snapshot", opfsPath: "...", wasmPath: "..." }, program: { source: "snapshot", opfsPath: "...", wasmPath: "..." },
runReadiness: { phase: "ready", canRun: true, buttonEnabled: true, programOpfsPath: "..." },
run: { status: "ok", canonicalEventCount: 2, motionSnapshotCount: 2, iniWasmPath: "..." }, run: { status: "ok", canonicalEventCount: 2, motionSnapshotCount: 2, iniWasmPath: "..." },
status: { lastAction: "run-gcode", error: null, fiveaxisRemap: "ok" } status: { lastAction: "run-gcode", error: null, fiveaxisRemap: "ok" }
} }
@@ -130,6 +132,7 @@ This helper derives a lightweight status object for upper-level control pages:
```js ```js
{ {
readiness: { ini: "...", interp: "...", opfs: "..." }, readiness: { ini: "...", interp: "...", opfs: "..." },
runReadiness: { phase: "ready", canRun: true, buttonEnabled: true },
lastAction: "run-gcode", lastAction: "run-gcode",
error: null, error: null,
session: { snapshotLabel: "...", workflow: "...", gcode: "..." }, session: { snapshotLabel: "...", workflow: "...", gcode: "..." },
@@ -167,7 +170,7 @@ This helper maps a bundle into UI-ready status and sections:
sections: [ sections: [
{ id: "readiness", title: "Readiness", rows: [{ label: "INI", value: "..." }] }, { id: "readiness", title: "Readiness", rows: [{ label: "INI", value: "..." }] },
{ id: "session", title: "Session", rows: [{ label: "Workflow", value: "..." }] }, { id: "session", title: "Session", rows: [{ label: "Workflow", value: "..." }] },
{ id: "run", title: "Run", rows: [{ label: "Canonical events", value: 2 }] } { id: "run", title: "Run", rows: [{ label: "Ready", value: "ready" }] }
] ]
} }
``` ```

View File

@@ -7,7 +7,7 @@ summarizing an INI panel G-code run.
`runtime/ui/ini-panel/run-workflow.js` contains the reusable UI workflow for `runtime/ui/ini-panel/run-workflow.js` contains the reusable UI workflow for
selecting the session/default G-code program, loading it from OPFS, staging it selecting the session/default G-code program, loading it from OPFS, staging it
into the interpreter WASM filesystem, invoking the LinuxCNC interpreter, and into the interpreter WASM filesystem, invoking the LinuxCNC interpreter, and
returning run field/log state. These helpers are intentionally pure so browser returning run readiness, field, and log state. These helpers are intentionally pure so browser
UI panels, SDK-facing wrappers, and Node smoke tests can share the same summary UI panels, SDK-facing wrappers, and Node smoke tests can share the same summary
shape without reading DOM text or parsing log output. shape without reading DOM text or parsing log output.
@@ -95,8 +95,8 @@ functions:
``` ```
It returns the selected program mapping, raw interpreter result text, run It returns the selected program mapping, raw interpreter result text, run
summary, UI field state, log lines, and optional UI motion snapshots produced summary, readiness state, UI field state, log lines, and optional UI motion
by the injected `summarizeRun` callback: snapshots produced by the injected `summarizeRun` callback:
```js ```js
{ {
@@ -108,6 +108,7 @@ by the injected `summarizeRun` callback:
}, },
resultText: "canon_event=...", resultText: "canon_event=...",
summary: { runStatus: "ok", programSource: "snapshot" }, summary: { runStatus: "ok", programSource: "snapshot" },
readiness: { phase: "ready", canRun: true },
fields: { runStatus: "ok", sessionGcode: "linuxcnc/gcode/ui-session.ngc" }, fields: { runStatus: "ok", sessionGcode: "linuxcnc/gcode/ui-session.ngc" },
motionSnapshots: [{ line: 1 }], motionSnapshots: [{ line: 1 }],
logLines: ["Program: linuxcnc/gcode/ui-session.ngc -> /work/ui-session.ngc"] logLines: ["Program: linuxcnc/gcode/ui-session.ngc -> /work/ui-session.ngc"]
@@ -117,6 +118,10 @@ by the injected `summarizeRun` callback:
`selectSessionGcodeProgram(input)` is the pure program-selection helper used by `selectSessionGcodeProgram(input)` is the pure program-selection helper used by
the workflow. Invalid snapshot G-code paths fall back to the UI default program. the workflow. Invalid snapshot G-code paths fall back to the UI default program.
`createRunSessionReadinessState(input)` is the pure readiness helper used by the
workflow and panel state summary. It reports the selected program, loaded
session path, snapshot metadata, and whether the run button can execute.
## Boundary Rules ## Boundary Rules
- Do not parse G-code text in these helpers. - Do not parse G-code text in these helpers.

View File

@@ -32,6 +32,7 @@ import {
summarizeRunResult, summarizeRunResult,
} from "./run-summary.js"; } from "./run-summary.js";
import { import {
createRunSessionReadinessState,
runGcodeSessionWorkflow, runGcodeSessionWorkflow,
} from "./run-workflow.js"; } from "./run-workflow.js";
import { import {
@@ -772,8 +773,28 @@ document.getElementById("restore-load-session").addEventListener("click", async
document.getElementById("run-gcode").addEventListener("click", async () => { document.getElementById("run-gcode").addEventListener("click", async () => {
try { try {
if (!loadedSession) { const readiness = createRunSessionReadinessState({
throw new Error("Load a machine session before running G-code."); loadedSession,
snapshot: restoredSessionSnapshot,
defaultFilename: UI_SESSION.defaultGcodeFilename,
defaultOpfsPath: defaultGcodeOpfsPath(),
wasmPath: UI_SESSION.gcodeWasmPath,
gcodeFilenameFromProgramPath,
});
updatePanelMachineSessionState({
runReadiness: readiness,
selectedProgram: readiness.program,
fields: {
sessionGcode: readiness.program.opfsPath,
runSource: readiness.program.source,
runOpfsPath: readiness.program.opfsPath,
runWasmPath: readiness.program.wasmPath,
},
lastAction: "run-gcode-readiness",
error: readiness.runButton.reason,
});
if (!readiness.canRun) {
throw new Error(readiness.runButton.reason ?? "Load a machine session before running G-code.");
} }
const interp = requireInterpSdk(); const interp = requireInterpSdk();
clearRunPlayback(); clearRunPlayback();
@@ -803,6 +824,7 @@ document.getElementById("run-gcode").addEventListener("click", async () => {
setField("runStatus", workflow.fields.runStatus); setField("runStatus", workflow.fields.runStatus);
setLastRunSummary(workflow.summary); setLastRunSummary(workflow.summary);
updatePanelMachineSessionState({ updatePanelMachineSessionState({
runReadiness: workflow.readiness,
selectedProgram: { selectedProgram: {
source: workflow.program.source, source: workflow.program.source,
opfsPath: workflow.program.opfsPath, opfsPath: workflow.program.opfsPath,

View File

@@ -4,6 +4,7 @@ export function createIniPanelStateSummary({
machineFiles = null, machineFiles = null,
sessionSnapshot = null, sessionSnapshot = null,
sessionLoad = null, sessionLoad = null,
runReadiness = null,
run = null, run = null,
fiveaxisRemap = null, fiveaxisRemap = null,
} = {}) { } = {}) {
@@ -17,6 +18,7 @@ export function createIniPanelStateSummary({
machineFiles, machineFiles,
sessionSnapshot, sessionSnapshot,
sessionLoad, sessionLoad,
runReadiness,
run, run,
fiveaxisRemap: fiveaxisRemap ?? null, fiveaxisRemap: fiveaxisRemap ?? null,
}; };
@@ -28,6 +30,7 @@ export function createMachineSessionStateSnapshot({
machineFiles = null, machineFiles = null,
sessionSnapshot = null, sessionSnapshot = null,
sessionLoad = null, sessionLoad = null,
runReadiness = null,
selectedProgram = null, selectedProgram = null,
run = null, run = null,
fields = {}, fields = {},
@@ -42,6 +45,7 @@ export function createMachineSessionStateSnapshot({
machineFiles, machineFiles,
sessionSnapshot, sessionSnapshot,
sessionLoad, sessionLoad,
runReadiness,
run, run,
fiveaxisRemap, fiveaxisRemap,
}), }),
@@ -69,6 +73,7 @@ export function createMachineSessionStateReport(state = {}) {
const fields = state.fields ?? {}; const fields = state.fields ?? {};
const sessionSnapshot = state.sessionSnapshot ?? {}; const sessionSnapshot = state.sessionSnapshot ?? {};
const sessionLoad = state.sessionLoad ?? {}; const sessionLoad = state.sessionLoad ?? {};
const runReadiness = state.runReadiness ?? {};
const selectedProgram = state.selectedProgram ?? {}; const selectedProgram = state.selectedProgram ?? {};
const run = state.run ?? {}; const run = state.run ?? {};
return { return {
@@ -95,6 +100,17 @@ export function createMachineSessionStateReport(state = {}) {
opfsPath: selectedProgram.opfsPath ?? run.programOpfsPath ?? fields.runOpfsPath ?? null, opfsPath: selectedProgram.opfsPath ?? run.programOpfsPath ?? fields.runOpfsPath ?? null,
wasmPath: selectedProgram.wasmPath ?? run.programWasmPath ?? fields.runWasmPath ?? null, wasmPath: selectedProgram.wasmPath ?? run.programWasmPath ?? fields.runWasmPath ?? null,
}, },
runReadiness: {
phase: runReadiness.phase ?? null,
canRun: Boolean(runReadiness.canRun),
missing: Array.isArray(runReadiness.missing) ? runReadiness.missing : [],
buttonEnabled: Boolean(runReadiness.runButton?.enabled),
buttonReason: runReadiness.runButton?.reason ?? null,
programSource: runReadiness.program?.source ?? null,
programOpfsPath: runReadiness.program?.opfsPath ?? null,
programWasmPath: runReadiness.program?.wasmPath ?? null,
iniWasmPath: runReadiness.loadedSession?.iniWasmPath ?? null,
},
run: { run: {
status: run.runStatus ?? fields.runStatus ?? null, status: run.runStatus ?? fields.runStatus ?? null,
canonicalEventCount: run.canonicalEventCount ?? 0, canonicalEventCount: run.canonicalEventCount ?? 0,
@@ -123,6 +139,7 @@ export function createMachineSessionWorkflowStatus(state = {}) {
const report = createMachineSessionStateReport(state); const report = createMachineSessionStateReport(state);
return { return {
readiness: report.readiness, readiness: report.readiness,
runReadiness: report.runReadiness,
lastAction: report.status.lastAction, lastAction: report.status.lastAction,
error: report.status.error, error: report.status.error,
session: report.session, session: report.session,
@@ -145,6 +162,7 @@ export function createMachineSessionControlView(bundle = {}) {
const report = bundle.report ?? {}; const report = bundle.report ?? {};
const workflowStatus = bundle.workflowStatus ?? {}; const workflowStatus = bundle.workflowStatus ?? {};
const session = workflowStatus.session ?? report.session ?? {}; const session = workflowStatus.session ?? report.session ?? {};
const runReadiness = workflowStatus.runReadiness ?? report.runReadiness ?? {};
const run = workflowStatus.run ?? report.run ?? {}; const run = workflowStatus.run ?? report.run ?? {};
return { return {
status: { status: {
@@ -176,6 +194,8 @@ export function createMachineSessionControlView(bundle = {}) {
id: "run", id: "run",
title: "Run", title: "Run",
rows: [ rows: [
{ label: "Ready", value: runReadiness.phase ?? null },
{ label: "Button", value: runReadiness.buttonEnabled ? "enabled" : "disabled" },
{ label: "Status", value: run.status ?? null }, { label: "Status", value: run.status ?? null },
{ label: "Canonical events", value: run.canonicalEventCount ?? 0 }, { label: "Canonical events", value: run.canonicalEventCount ?? 0 },
{ label: "Motion snapshots", value: run.motionSnapshotCount ?? 0 }, { label: "Motion snapshots", value: run.motionSnapshotCount ?? 0 },

View File

@@ -34,6 +34,55 @@ export function selectSessionGcodeProgram({
}; };
} }
export function createRunSessionReadinessState({
loadedSession = null,
snapshot = null,
defaultFilename,
defaultOpfsPath,
wasmPath,
gcodeFilenameFromProgramPath,
}) {
const program = selectSessionGcodeProgram({
snapshot,
defaultFilename,
defaultOpfsPath,
wasmPath,
gcodeFilenameFromProgramPath,
});
const missing = [];
if (!loadedSession?.ini?.wasmPath) {
missing.push("loaded-session");
}
if (!program.filename || !program.opfsPath || !program.wasmPath) {
missing.push("gcode-program");
}
const canRun = missing.length === 0;
return {
phase: canRun ? "ready" : "blocked",
canRun,
missing,
loadedSession: {
iniWasmPath: loadedSession?.ini?.wasmPath ?? null,
},
snapshot: {
present: Boolean(snapshot),
label: snapshot?.metadata?.snapshotLabel ?? snapshot?.sessionId ?? null,
workflow: snapshot?.metadata?.workflow ?? null,
gcodeOpfsPath: snapshot?.payload?.files?.gcode ?? null,
},
defaultProgram: {
filename: defaultFilename ?? null,
opfsPath: defaultOpfsPath ?? null,
wasmPath: wasmPath ?? null,
},
program,
runButton: {
enabled: canRun,
reason: canRun ? null : `Missing ${missing.join(", ")}.`,
},
};
}
export function runSessionContextLogLines(program, snapshot = null, snapshotLabel = null) { export function runSessionContextLogLines(program, snapshot = null, snapshotLabel = null) {
const summary = createSessionSnapshotSummary({ const summary = createSessionSnapshotSummary({
metadata: snapshot?.metadata ?? {}, metadata: snapshot?.metadata ?? {},
@@ -58,16 +107,18 @@ export async function runGcodeSessionWorkflow({
summarizeRun = () => ({}), summarizeRun = () => ({}),
snapshotLabel = null, snapshotLabel = null,
}) { }) {
if (!loadedSession) { const readiness = createRunSessionReadinessState({
throw new Error("Load a machine session before running G-code."); loadedSession,
}
const program = selectSessionGcodeProgram({
snapshot, snapshot,
defaultFilename, defaultFilename,
defaultOpfsPath, defaultOpfsPath,
wasmPath, wasmPath,
gcodeFilenameFromProgramPath, gcodeFilenameFromProgramPath,
}); });
if (!readiness.canRun) {
throw new Error("Load a machine session before running G-code.");
}
const program = readiness.program;
const programText = await loadGcodeProgram(program.filename); const programText = await loadGcodeProgram(program.filename);
interp.writeTextFile(program.wasmPath, programText); interp.writeTextFile(program.wasmPath, programText);
const resultText = interp.runFileWithIni(program.wasmPath, loadedSession.ini.wasmPath).trim(); const resultText = interp.runFileWithIni(program.wasmPath, loadedSession.ini.wasmPath).trim();
@@ -91,6 +142,7 @@ export async function runGcodeSessionWorkflow({
resultText, resultText,
motionSnapshots, motionSnapshots,
summary, summary,
readiness,
fields: { fields: {
sessionGcode: program.opfsPath, sessionGcode: program.opfsPath,
runStatus: summary.runStatus, runStatus: summary.runStatus,

View File

@@ -1,6 +1,7 @@
import assert from "node:assert/strict"; import assert from "node:assert/strict";
import { import {
createRunSessionReadinessState,
runGcodeSessionWorkflow, runGcodeSessionWorkflow,
selectSessionGcodeProgram, selectSessionGcodeProgram,
} from "../../../runtime/ui/ini-panel/run-workflow.js"; } from "../../../runtime/ui/ini-panel/run-workflow.js";
@@ -42,6 +43,61 @@ assert.deepEqual(snapshotProgram, {
wasmPath: "/work/ui-session.ngc", wasmPath: "/work/ui-session.ngc",
}); });
const readiness = createRunSessionReadinessState({
loadedSession: {
ini: {
wasmPath: "/work/session-machine.ini",
},
},
snapshot: {
sessionId: "ui-machine-session",
metadata: {
workflow: "save-session-snapshot",
snapshotLabel: "ui-machine-session/ui-machine-session.json",
defaultGcodeOpfsPath: "linuxcnc/gcode/ui-session.ngc",
},
payload: {
files: {
gcode: "linuxcnc/gcode/snapshot-session.ngc",
},
},
},
defaultFilename: "ui-session.ngc",
defaultOpfsPath: "linuxcnc/gcode/ui-session.ngc",
wasmPath: "/work/ui-session.ngc",
gcodeFilenameFromProgramPath: (path) => path.split("/").at(-1),
});
assert.deepEqual(readiness, {
phase: "ready",
canRun: true,
missing: [],
loadedSession: {
iniWasmPath: "/work/session-machine.ini",
},
snapshot: {
present: true,
label: "ui-machine-session/ui-machine-session.json",
workflow: "save-session-snapshot",
gcodeOpfsPath: "linuxcnc/gcode/snapshot-session.ngc",
},
defaultProgram: {
filename: "ui-session.ngc",
opfsPath: "linuxcnc/gcode/ui-session.ngc",
wasmPath: "/work/ui-session.ngc",
},
program: {
filename: "snapshot-session.ngc",
opfsPath: "linuxcnc/gcode/snapshot-session.ngc",
source: "snapshot",
wasmPath: "/work/ui-session.ngc",
},
runButton: {
enabled: true,
reason: null,
},
});
const calls = []; const calls = [];
const workflow = await runGcodeSessionWorkflow({ const workflow = await runGcodeSessionWorkflow({
interp: { interp: {
@@ -126,6 +182,7 @@ assert.deepEqual(workflow.fields, {
runOpfsPath: "linuxcnc/gcode/snapshot-session.ngc", runOpfsPath: "linuxcnc/gcode/snapshot-session.ngc",
runWasmPath: "/work/ui-session.ngc", runWasmPath: "/work/ui-session.ngc",
}); });
assert.deepEqual(workflow.readiness, readiness);
assert.deepEqual(workflow.motionSnapshots, [{ line: 1 }]); assert.deepEqual(workflow.motionSnapshots, [{ line: 1 }]);

View File

@@ -13,6 +13,60 @@ import {
renderMachineSessionControlView, renderMachineSessionControlView,
} from "../../../runtime/ui/ini-panel/control-view-renderer.js"; } from "../../../runtime/ui/ini-panel/control-view-renderer.js";
const runReadinessFixture = {
phase: "ready",
canRun: true,
missing: [],
loadedSession: {
iniWasmPath: "/work/session-machine.ini",
},
snapshot: {
present: true,
label: "ui-machine-session/ui-machine-session.json",
workflow: "save-session-snapshot",
gcodeOpfsPath: "linuxcnc/gcode/ui-session.ngc",
},
defaultProgram: {
filename: "ui-session.ngc",
opfsPath: "linuxcnc/gcode/ui-session.ngc",
wasmPath: "/work/ui-session.ngc",
},
program: {
filename: "ui-session.ngc",
opfsPath: "linuxcnc/gcode/ui-session.ngc",
source: "default",
wasmPath: "/work/ui-session.ngc",
},
runButton: {
enabled: true,
reason: null,
},
};
const expectedRunReadinessReport = {
phase: "ready",
canRun: true,
missing: [],
buttonEnabled: true,
buttonReason: null,
programSource: "default",
programOpfsPath: "linuxcnc/gcode/ui-session.ngc",
programWasmPath: "/work/ui-session.ngc",
iniWasmPath: "/work/session-machine.ini",
};
const expectedEmptyRunReadinessReport = {
phase: null,
canRun: false,
missing: [],
buttonEnabled: false,
buttonReason: null,
programSource: null,
programOpfsPath: null,
programWasmPath: null,
iniWasmPath: null,
};
const summary = createIniPanelStateSummary({ const summary = createIniPanelStateSummary({
badges: { badges: {
ini: "INI WASM: ready", ini: "INI WASM: ready",
@@ -37,6 +91,7 @@ const summary = createIniPanelStateSummary({
iniOpfsPath: "linuxcnc/machines/xyzab-tdr/machine.ini", iniOpfsPath: "linuxcnc/machines/xyzab-tdr/machine.ini",
iniWasmPath: "/work/session-machine.ini", iniWasmPath: "/work/session-machine.ini",
}, },
runReadiness: runReadinessFixture,
run: { run: {
runStatus: "ok", runStatus: "ok",
programSource: "snapshot", programSource: "snapshot",
@@ -68,6 +123,7 @@ assert.deepEqual(summary, {
iniOpfsPath: "linuxcnc/machines/xyzab-tdr/machine.ini", iniOpfsPath: "linuxcnc/machines/xyzab-tdr/machine.ini",
iniWasmPath: "/work/session-machine.ini", iniWasmPath: "/work/session-machine.ini",
}, },
runReadiness: runReadinessFixture,
run: { run: {
runStatus: "ok", runStatus: "ok",
programSource: "snapshot", programSource: "snapshot",
@@ -87,6 +143,7 @@ assert.deepEqual(
machineFiles: null, machineFiles: null,
sessionSnapshot: null, sessionSnapshot: null,
sessionLoad: null, sessionLoad: null,
runReadiness: null,
run: null, run: null,
fiveaxisRemap: null, fiveaxisRemap: null,
}, },
@@ -108,6 +165,7 @@ assert.deepEqual(
sessionLoad: { sessionLoad: {
iniWasmPath: "/work/session-machine.ini", iniWasmPath: "/work/session-machine.ini",
}, },
runReadiness: runReadinessFixture,
selectedProgram: { selectedProgram: {
source: "snapshot", source: "snapshot",
opfsPath: "linuxcnc/gcode/ui-session.ngc", opfsPath: "linuxcnc/gcode/ui-session.ngc",
@@ -149,6 +207,7 @@ assert.deepEqual(
sessionLoad: { sessionLoad: {
iniWasmPath: "/work/session-machine.ini", iniWasmPath: "/work/session-machine.ini",
}, },
runReadiness: runReadinessFixture,
run: { run: {
runStatus: "ok", runStatus: "ok",
programSource: "snapshot", programSource: "snapshot",
@@ -219,6 +278,7 @@ assert.deepEqual(
parameterWasmPath: "/work/session-linuxcnc.var", parameterWasmPath: "/work/session-linuxcnc.var",
toolTableWasmPath: "/work/session-tool.tbl", toolTableWasmPath: "/work/session-tool.tbl",
}, },
runReadiness: runReadinessFixture,
selectedProgram: { selectedProgram: {
source: "snapshot", source: "snapshot",
opfsPath: "linuxcnc/gcode/ui-session.ngc", opfsPath: "linuxcnc/gcode/ui-session.ngc",
@@ -261,6 +321,7 @@ assert.deepEqual(
opfsPath: "linuxcnc/gcode/ui-session.ngc", opfsPath: "linuxcnc/gcode/ui-session.ngc",
wasmPath: "/work/ui-session.ngc", wasmPath: "/work/ui-session.ngc",
}, },
runReadiness: expectedRunReadinessReport,
run: { run: {
status: "ok", status: "ok",
canonicalEventCount: 2, canonicalEventCount: 2,
@@ -299,6 +360,7 @@ const expectedExportReport = {
opfsPath: null, opfsPath: null,
wasmPath: null, wasmPath: null,
}, },
runReadiness: expectedEmptyRunReadinessReport,
run: { run: {
status: null, status: null,
canonicalEventCount: 0, canonicalEventCount: 0,
@@ -350,6 +412,7 @@ assert.deepEqual(
runStatus: "ok", runStatus: "ok",
runWorkflow: "save-session-snapshot", runWorkflow: "save-session-snapshot",
}, },
runReadiness: runReadinessFixture,
run: { run: {
runStatus: "ok", runStatus: "ok",
canonicalEventCount: 2, canonicalEventCount: 2,
@@ -364,6 +427,7 @@ assert.deepEqual(
interp: "Interpreter WASM: ready", interp: "Interpreter WASM: ready",
opfs: "OPFS: ready", opfs: "OPFS: ready",
}, },
runReadiness: expectedRunReadinessReport,
lastAction: "run-gcode", lastAction: "run-gcode",
error: null, error: null,
session: { session: {
@@ -407,6 +471,7 @@ const expectedBundleReport = {
opfsPath: null, opfsPath: null,
wasmPath: null, wasmPath: null,
}, },
runReadiness: expectedRunReadinessReport,
run: { run: {
status: "ok", status: "ok",
canonicalEventCount: 2, canonicalEventCount: 2,
@@ -434,6 +499,7 @@ assert.deepEqual(
runStatus: "ok", runStatus: "ok",
runWorkflow: "save-session-snapshot", runWorkflow: "save-session-snapshot",
}, },
runReadiness: runReadinessFixture,
run: { run: {
runStatus: "ok", runStatus: "ok",
canonicalEventCount: 2, canonicalEventCount: 2,
@@ -458,6 +524,7 @@ assert.deepEqual(
}, },
lastAction: "run-gcode", lastAction: "run-gcode",
error: null, error: null,
runReadiness: expectedRunReadinessReport,
session: { session: {
snapshotLabel: "ui-machine-session/ui-machine-session.json", snapshotLabel: "ui-machine-session/ui-machine-session.json",
workflow: "save-session-snapshot", workflow: "save-session-snapshot",
@@ -501,6 +568,7 @@ assert.deepEqual(
toolTable: null, toolTable: null,
gcode: "linuxcnc/gcode/ui-session.ngc", gcode: "linuxcnc/gcode/ui-session.ngc",
}, },
runReadiness: expectedRunReadinessReport,
run: { run: {
status: "ok", status: "ok",
canonicalEventCount: 2, canonicalEventCount: 2,
@@ -539,6 +607,8 @@ assert.deepEqual(
id: "run", id: "run",
title: "Run", title: "Run",
rows: [ rows: [
{ label: "Ready", value: "ready" },
{ label: "Button", value: "enabled" },
{ label: "Status", value: "ok" }, { label: "Status", value: "ok" },
{ label: "Canonical events", value: 2 }, { label: "Canonical events", value: 2 },
{ label: "Motion snapshots", value: 2 }, { label: "Motion snapshots", value: 2 },
@@ -578,6 +648,7 @@ const renderedControlView = renderMachineSessionControlView(
lastAction: "run-gcode", lastAction: "run-gcode",
error: null, error: null,
session: expectedBundleReport.session, session: expectedBundleReport.session,
runReadiness: expectedBundleReport.runReadiness,
run: expectedBundleReport.run, run: expectedBundleReport.run,
}, },
}), }),
@@ -588,7 +659,7 @@ assert.equal(renderedControlView.sectionNodes.length, 3);
assert.equal(renderedControlView.sectionNodes[2].dataset.sectionId, "run"); assert.equal(renderedControlView.sectionNodes[2].dataset.sectionId, "run");
assert.equal( assert.equal(
renderedControlView.sectionNodes[2].children[1].children[3].textContent, renderedControlView.sectionNodes[2].children[1].children[3].textContent,
"2", "enabled",
); );
assert.deepEqual( assert.deepEqual(