推进 INI 面板 workflow overview helper

This commit is contained in:
2026-06-15 05:32:09 +08:00
parent df1f39974f
commit deb8b7cdd3
5 changed files with 247 additions and 4 deletions

View File

@@ -515,3 +515,75 @@ host_wasm_opfs_browser_smokes=ok
`restore-load-session` 的 field/log 路径再做一次轻量整合,优先把“已加载 session、快照恢复、 `restore-load-session` 的 field/log 路径再做一次轻量整合,优先把“已加载 session、快照恢复、
run readiness、run 结果”汇总成一个更上层的只读工作流状态对象;仍保持只描述 UI/host run readiness、run 结果”汇总成一个更上层的只读工作流状态对象;仍保持只描述 UI/host
boundary facts不在 JS 中解释 G-code、canonical events 或 CNC 运行语义。 boundary facts不在 JS 中解释 G-code、canonical events 或 CNC 运行语义。
五、2026-06-15 继续执行记录INI panel workflow overview helper
本轮按“第一原则:加快实质性推进”继续推进更上层只读工作流状态对象,将已归一化的
report、run readiness、session snapshot/load、run result 和 control page 需要的状态字段
再压成单一 workflow overview供 state bundle、workflow status 和 read-only control view
共享。
完成内容:
- 新增 `wasm-port/runtime/ui/ini-panel/panel-state-summary.js` 的
`createMachineSessionWorkflowOverview(report)`
- `createMachineSessionWorkflowStatus()` 现在额外返回 `overview`
- `createMachineSessionStateBundle()` 现在额外返回 `overview`
- `createMachineSessionControlView()` 新增 `Workflow` 区,显示:
- `Phase`
- `Missing`
- `Last action`
- `Error`
- overview 只组合已有的 readiness、session、loaded、runReadiness、run 和 status 信息;
- `Missing` 现在优先基于 loaded session / run readiness 的已知边界事实判断,不再依赖 DOM
- `wasm-port/tests/ui/node/verify_ini_panel_state_summary.mjs` 覆盖 overview helper、bundle、
workflow status 和 control view
- `wasm-port/tests/ui/node/verify_ini_panel_control_page_session_workflow.mjs` 同步带上 overview
- `wasm-port/docs/ui-panel-state-summary.md` 同步说明 overview helper
- 未新增控制按钮;
- 未解析 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_state_summary.sh
wasm-port/tests/ui/node/verify_ini_panel_control_page_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_state_summary_node_smoke=ok
ini_panel_control_page_session_workflow_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`
和 `run` 的状态入口再向上收拢成一个稳定的只读状态 API优先服务 control page 和 shell 的
统一读取;仍保持只描述 UI/host boundary facts不在 JS 中解释 G-code、canonical events 或
CNC 运行语义。

View File

@@ -132,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: "..." },
overview: { phase: "ran", missing: [], status: { lastAction: "run-gcode" } },
runReadiness: { phase: "ready", canRun: true, buttonEnabled: true }, runReadiness: { phase: "ready", canRun: true, buttonEnabled: true },
lastAction: "run-gcode", lastAction: "run-gcode",
error: null, error: null,
@@ -143,6 +144,27 @@ This helper derives a lightweight status object for upper-level control pages:
It is intended for polling or status panels that need current UI workflow It is intended for polling or status panels that need current UI workflow
state without parsing logs, DOM nodes, or transient window state. state without parsing logs, DOM nodes, or transient window state.
## `createMachineSessionWorkflowOverview(report)`
This helper folds the already-normalized report into a single read-only
workflow object for shell and control-page consumers:
```js
{
phase: "waiting" | "ready" | "ran",
missing: [],
readiness: { ini: "...", interp: "...", opfs: "..." },
session: { snapshotLabel: "...", workflow: "...", ini: "...", gcode: "..." },
loaded: { iniWasmPath: "...", parameterWasmPath: "...", toolTableWasmPath: "..." },
runReadiness: { phase: "ready", canRun: true },
run: { status: "ok", canonicalEventCount: 2, motionSnapshotCount: 2 },
status: { lastAction: "run-gcode", error: null }
}
```
It combines existing UI/host boundary facts only. It does not inspect G-code
or derive CNC behavior.
## `createMachineSessionStateBundle(state, options)` ## `createMachineSessionStateBundle(state, options)`
This helper packages the current report, export payload, and workflow status This helper packages the current report, export payload, and workflow status
@@ -151,8 +173,9 @@ into one read-only object:
```js ```js
{ {
report: { ... }, report: { ... },
overview: { phase: "ran", missing: [] },
export: { filename: "...", mediaType: "application/json", report: { ... }, text: "{...}\n" }, export: { filename: "...", mediaType: "application/json", report: { ... }, text: "{...}\n" },
workflowStatus: { readiness: { ... }, lastAction: "...", error: null, session: { ... }, run: { ... } } workflowStatus: { readiness: { ... }, overview: { ... }, lastAction: "...", session: { ... }, run: { ... } }
} }
``` ```
@@ -170,6 +193,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: "workflow", title: "Workflow", rows: [{ label: "Phase", value: "ran" }] },
{ id: "run", title: "Run", rows: [{ label: "Ready", value: "ready" }] } { id: "run", title: "Run", rows: [{ label: "Ready", value: "ready" }] }
] ]
} }

View File

@@ -137,8 +137,10 @@ export function createMachineSessionStateReportExport(state = {}, options = {})
export function createMachineSessionWorkflowStatus(state = {}) { export function createMachineSessionWorkflowStatus(state = {}) {
const report = createMachineSessionStateReport(state); const report = createMachineSessionStateReport(state);
const overview = createMachineSessionWorkflowOverview(report);
return { return {
readiness: report.readiness, readiness: report.readiness,
overview,
runReadiness: report.runReadiness, runReadiness: report.runReadiness,
lastAction: report.status.lastAction, lastAction: report.status.lastAction,
error: report.status.error, error: report.status.error,
@@ -147,12 +149,57 @@ export function createMachineSessionWorkflowStatus(state = {}) {
}; };
} }
export function createMachineSessionWorkflowOverview(report = {}) {
const readiness = report.readiness ?? {};
const loaded = report.loaded ?? {};
const session = report.session ?? {};
const runReadiness = report.runReadiness ?? {};
const run = report.run ?? {};
const status = report.status ?? {};
const loadedIniWasmPath = loaded.iniWasmPath ?? runReadiness.iniWasmPath ?? null;
const missing = [];
if (!loadedIniWasmPath) {
missing.push("loaded-session");
}
if (!session.snapshotLabel) {
missing.push("session-snapshot");
}
if (!runReadiness.canRun) {
missing.push("run-readiness");
}
return {
phase: run.status ? "ran" : (missing.length === 0 ? "ready" : "waiting"),
missing,
readiness,
session: {
snapshotLabel: session.snapshotLabel ?? null,
workflow: session.workflow ?? null,
ini: session.ini ?? null,
gcode: session.gcode ?? null,
},
loaded,
runReadiness,
run: {
status: run.status ?? null,
canonicalEventCount: run.canonicalEventCount ?? 0,
motionSnapshotCount: run.motionSnapshotCount ?? 0,
iniWasmPath: run.iniWasmPath ?? null,
},
status: {
lastAction: status.lastAction ?? null,
error: status.error ?? null,
},
};
}
export function createMachineSessionStateBundle(state = {}, options = {}) { export function createMachineSessionStateBundle(state = {}, options = {}) {
const report = createMachineSessionStateReport(state); const report = createMachineSessionStateReport(state);
const exportPayload = createMachineSessionStateReportExport(state, options); const exportPayload = createMachineSessionStateReportExport(state, options);
const workflowStatus = createMachineSessionWorkflowStatus(state); const workflowStatus = createMachineSessionWorkflowStatus(state);
const overview = createMachineSessionWorkflowOverview(report);
return { return {
report, report,
overview,
export: exportPayload, export: exportPayload,
workflowStatus, workflowStatus,
}; };
@@ -161,6 +208,7 @@ export function createMachineSessionStateBundle(state = {}, options = {}) {
export function createMachineSessionControlView(bundle = {}) { export function createMachineSessionControlView(bundle = {}) {
const report = bundle.report ?? {}; const report = bundle.report ?? {};
const workflowStatus = bundle.workflowStatus ?? {}; const workflowStatus = bundle.workflowStatus ?? {};
const overview = bundle.overview ?? workflowStatus.overview ?? {};
const session = workflowStatus.session ?? report.session ?? {}; const session = workflowStatus.session ?? report.session ?? {};
const runReadiness = workflowStatus.runReadiness ?? report.runReadiness ?? {}; const runReadiness = workflowStatus.runReadiness ?? report.runReadiness ?? {};
const run = workflowStatus.run ?? report.run ?? {}; const run = workflowStatus.run ?? report.run ?? {};
@@ -190,6 +238,16 @@ export function createMachineSessionControlView(bundle = {}) {
{ label: "G-code", value: session.gcode ?? null }, { label: "G-code", value: session.gcode ?? null },
], ],
}, },
{
id: "workflow",
title: "Workflow",
rows: [
{ label: "Phase", value: overview.phase ?? null },
{ label: "Missing", value: overview.missing?.join(", ") || null },
{ label: "Last action", value: overview.status?.lastAction ?? null },
{ label: "Error", value: overview.status?.error ?? null },
],
},
{ {
id: "run", id: "run",
title: "Run", title: "Run",

View File

@@ -13,8 +13,14 @@ const stateBundle = {
opfs: "OPFS: ready", opfs: "OPFS: ready",
}, },
}, },
overview: {
phase: "ready",
},
}; };
const workflowStatus = { const workflowStatus = {
overview: {
phase: "ready",
},
lastAction: "load-session", lastAction: "load-session",
error: null, error: null,
}; };

View File

@@ -7,6 +7,7 @@ import {
createMachineSessionStateBundle, createMachineSessionStateBundle,
createMachineSessionStateReport, createMachineSessionStateReport,
createMachineSessionStateReportExport, createMachineSessionStateReportExport,
createMachineSessionWorkflowOverview,
createMachineSessionWorkflowStatus, createMachineSessionWorkflowStatus,
} from "../../../runtime/ui/ini-panel/panel-state-summary.js"; } from "../../../runtime/ui/ini-panel/panel-state-summary.js";
import { import {
@@ -67,6 +68,38 @@ const expectedEmptyRunReadinessReport = {
iniWasmPath: null, iniWasmPath: null,
}; };
const expectedWorkflowOverview = {
phase: "ran",
missing: [],
readiness: {
ini: "INI WASM: ready",
interp: "Interpreter WASM: ready",
opfs: "OPFS: ready",
},
session: {
snapshotLabel: "ui-machine-session/ui-machine-session.json",
workflow: "save-session-snapshot",
ini: "/work/session-machine.ini",
gcode: "linuxcnc/gcode/ui-session.ngc",
},
loaded: {
iniWasmPath: null,
parameterWasmPath: null,
toolTableWasmPath: null,
},
runReadiness: expectedRunReadinessReport,
run: {
status: "ok",
canonicalEventCount: 2,
motionSnapshotCount: 2,
iniWasmPath: null,
},
status: {
lastAction: "run-gcode",
error: null,
},
};
const summary = createIniPanelStateSummary({ const summary = createIniPanelStateSummary({
badges: { badges: {
ini: "INI WASM: ready", ini: "INI WASM: ready",
@@ -427,6 +460,37 @@ assert.deepEqual(
interp: "Interpreter WASM: ready", interp: "Interpreter WASM: ready",
opfs: "OPFS: ready", opfs: "OPFS: ready",
}, },
overview: {
phase: "ran",
missing: [],
readiness: {
ini: "INI WASM: ready",
interp: "Interpreter WASM: ready",
opfs: "OPFS: ready",
},
session: {
snapshotLabel: "ui-machine-session/ui-machine-session.json",
workflow: "save-session-snapshot",
ini: "/work/session-machine.ini",
gcode: "linuxcnc/gcode/ui-session.ngc",
},
loaded: {
iniWasmPath: null,
parameterWasmPath: null,
toolTableWasmPath: null,
},
runReadiness: expectedRunReadinessReport,
run: {
status: "ok",
canonicalEventCount: 2,
motionSnapshotCount: 2,
iniWasmPath: null,
},
status: {
lastAction: "run-gcode",
error: null,
},
},
runReadiness: expectedRunReadinessReport, runReadiness: expectedRunReadinessReport,
lastAction: "run-gcode", lastAction: "run-gcode",
error: null, error: null,
@@ -485,6 +549,11 @@ const expectedBundleReport = {
}, },
}; };
assert.deepEqual(
createMachineSessionWorkflowOverview(expectedBundleReport),
expectedWorkflowOverview,
);
assert.deepEqual( assert.deepEqual(
createMachineSessionStateBundle({ createMachineSessionStateBundle({
badges: { badges: {
@@ -510,6 +579,7 @@ assert.deepEqual(
}), }),
{ {
report: expectedBundleReport, report: expectedBundleReport,
overview: expectedWorkflowOverview,
export: { export: {
filename: "machine-session-state-report.json", filename: "machine-session-state-report.json",
mediaType: "application/json", mediaType: "application/json",
@@ -522,6 +592,7 @@ assert.deepEqual(
interp: "Interpreter WASM: ready", interp: "Interpreter WASM: ready",
opfs: "OPFS: ready", opfs: "OPFS: ready",
}, },
overview: expectedWorkflowOverview,
lastAction: "run-gcode", lastAction: "run-gcode",
error: null, error: null,
runReadiness: expectedRunReadinessReport, runReadiness: expectedRunReadinessReport,
@@ -560,6 +631,7 @@ assert.deepEqual(
}, },
lastAction: "run-gcode", lastAction: "run-gcode",
error: null, error: null,
overview: expectedWorkflowOverview,
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",
@@ -603,6 +675,16 @@ assert.deepEqual(
{ label: "G-code", value: "linuxcnc/gcode/ui-session.ngc" }, { label: "G-code", value: "linuxcnc/gcode/ui-session.ngc" },
], ],
}, },
{
id: "workflow",
title: "Workflow",
rows: [
{ label: "Phase", value: "ran" },
{ label: "Missing", value: null },
{ label: "Last action", value: "run-gcode" },
{ label: "Error", value: null },
],
},
{ {
id: "run", id: "run",
title: "Run", title: "Run",
@@ -645,6 +727,7 @@ const renderedControlView = renderMachineSessionControlView(
report: expectedBundleReport, report: expectedBundleReport,
workflowStatus: { workflowStatus: {
readiness: expectedBundleReport.readiness, readiness: expectedBundleReport.readiness,
overview: expectedWorkflowOverview,
lastAction: "run-gcode", lastAction: "run-gcode",
error: null, error: null,
session: expectedBundleReport.session, session: expectedBundleReport.session,
@@ -655,10 +738,10 @@ const renderedControlView = renderMachineSessionControlView(
); );
assert.equal(renderedControlView.statusNode.textContent, "run-gcode | ok | -"); assert.equal(renderedControlView.statusNode.textContent, "run-gcode | ok | -");
assert.equal(renderedControlView.sectionNodes.length, 3); assert.equal(renderedControlView.sectionNodes.length, 4);
assert.equal(renderedControlView.sectionNodes[2].dataset.sectionId, "run"); assert.equal(renderedControlView.sectionNodes[3].dataset.sectionId, "run");
assert.equal( assert.equal(
renderedControlView.sectionNodes[2].children[1].children[3].textContent, renderedControlView.sectionNodes[3].children[1].children[3].textContent,
"enabled", "enabled",
); );