推进 INI 面板 control view helper

This commit is contained in:
2026-06-14 22:40:47 +08:00
parent e6f55d775f
commit 4133d202f0
6 changed files with 237 additions and 2 deletions

View File

@@ -958,3 +958,64 @@ host_wasm_opfs_browser_smokes=ok
继续沿“单点只读入口”推进,把 `window.linuxCncIniPanelApi` 再收敛成一个更明确的
`getMachineSessionView()` 或 `getMachineSessionStateBundle()` 的稳定消费者契约,并开始只
在这个 bundle 上面叠控制页展示逻辑;仍不新增按钮、不把 CNC 语义搬进 JS。
十、2026-06-14 继续执行记录INI panel control view helper
本轮按“加快实质性推进”继续往控制网页界面的只读展示层推进,在 bundle 之上新增
control view helper让上层 UI 可以直接渲染 status 与 sections/rows。
完成内容:
- `wasm-port/runtime/ui/ini-panel/panel-state-summary.js` 新增
`createMachineSessionControlView(bundle)`
- control view 返回:
- `status`
- `sections`
- readiness/session/run 三组 rows
- `wasm-port/runtime/ui/ini-panel/app.js` 在
`window.linuxCncIniPanelApi`、`window.getMachineSessionControlView()` 下暴露该 helper
- `wasm-port/tests/ui/node/verify_ini_panel_state_summary.mjs` 覆盖 view model 输出结构;
- `wasm-port/tests/browser/ini_panel_smoke.html` 在真实 save、restore-load、run 流程后验证
control view 的 status、section id、workflow row 和 canonical-event row
- `wasm-port/docs/ui-panel-state-summary.md` 补充 control view 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_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
ui_node_smokes=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
host_wasm_opfs_browser_smokes=ok
```
下一步建议:
继续推进实际控制网页界面的只读展示层。下一批建议新增一个极薄的 browser smoke 或
文档化控制页消费示例,直接读取 `window.linuxCncIniPanelApi.getMachineSessionControlView()`
并渲染 sections仍保持只读不加控制按钮不解析 G-code。

View File

@@ -22,6 +22,7 @@ For control-page polling, use
`window.linuxCncIniPanelApi.getMachineSessionWorkflowStatus()`. It returns a
smaller workflow-facing object derived from the same report data.
For a single read call, use `window.linuxCncIniPanelApi.getMachineSessionStateBundle()`.
For direct rendering, use `window.linuxCncIniPanelApi.getMachineSessionControlView()`.
## `createIniPanelStateSummary(input)`
@@ -142,6 +143,24 @@ It is the preferred single-entry helper for control-page polling and other
upper-level UI consumers that want one stable read instead of stitching the
three views together themselves.
## `createMachineSessionControlView(bundle)`
This helper maps a bundle into UI-ready status and sections:
```js
{
status: { lastAction: "run-gcode", error: null, runStatus: "ok" },
sections: [
{ id: "readiness", title: "Readiness", rows: [{ label: "INI", value: "..." }] },
{ id: "session", title: "Session", rows: [{ label: "Workflow", value: "..." }] },
{ id: "run", title: "Run", rows: [{ label: "Canonical events", value: 2 }] }
]
}
```
It is a presentation mapping over the bundle only. It does not parse G-code,
canonical events, or LinuxCNC-owned runtime content.
## Boundary Rules
- Do not parse G-code text in this helper.

View File

@@ -46,6 +46,7 @@ import {
saveMachineSessionSnapshotWorkflow,
} from "./session-workflow.js";
import {
createMachineSessionControlView,
createMachineSessionStateSnapshot,
createMachineSessionStateBundle,
createMachineSessionStateReport,
@@ -171,6 +172,10 @@ function getMachineSessionStateBundle() {
return createMachineSessionStateBundle(window.linuxCncIniPanelMachineSessionState);
}
function getMachineSessionControlView() {
return createMachineSessionControlView(getMachineSessionStateBundle());
}
function updatePanelMachineSessionState(patch = {}) {
panelMachineSessionState = createMachineSessionStateSnapshot({
...panelMachineSessionState,
@@ -185,11 +190,13 @@ function updatePanelMachineSessionState(patch = {}) {
}
window.linuxCncIniPanelApi = {
getMachineSessionControlView,
getMachineSessionStateBundle,
getMachineSessionStateReport,
getMachineSessionStateReportExport,
getMachineSessionWorkflowStatus,
};
window.getMachineSessionControlView = getMachineSessionControlView;
window.getMachineSessionStateBundle = getMachineSessionStateBundle;
window.getMachineSessionStateReport = getMachineSessionStateReport;
window.getMachineSessionStateReportExport = getMachineSessionStateReportExport;

View File

@@ -140,3 +140,48 @@ export function createMachineSessionStateBundle(state = {}, options = {}) {
workflowStatus,
};
}
export function createMachineSessionControlView(bundle = {}) {
const report = bundle.report ?? {};
const workflowStatus = bundle.workflowStatus ?? {};
const session = workflowStatus.session ?? report.session ?? {};
const run = workflowStatus.run ?? report.run ?? {};
return {
status: {
lastAction: workflowStatus.lastAction ?? report.status?.lastAction ?? null,
error: workflowStatus.error ?? report.status?.error ?? null,
runStatus: run.status ?? null,
},
sections: [
{
id: "readiness",
title: "Readiness",
rows: [
{ label: "INI", value: workflowStatus.readiness?.ini ?? report.readiness?.ini ?? null },
{ label: "Interpreter", value: workflowStatus.readiness?.interp ?? report.readiness?.interp ?? null },
{ label: "OPFS", value: workflowStatus.readiness?.opfs ?? report.readiness?.opfs ?? null },
],
},
{
id: "session",
title: "Session",
rows: [
{ label: "Snapshot", value: session.snapshotLabel ?? null },
{ label: "Workflow", value: session.workflow ?? null },
{ label: "INI", value: session.ini ?? null },
{ label: "G-code", value: session.gcode ?? null },
],
},
{
id: "run",
title: "Run",
rows: [
{ label: "Status", value: run.status ?? null },
{ label: "Canonical events", value: run.canonicalEventCount ?? 0 },
{ label: "Motion snapshots", value: run.motionSnapshotCount ?? 0 },
{ label: "INI WASM", value: run.iniWasmPath ?? null },
],
},
],
};
}

View File

@@ -369,6 +369,7 @@ TOOL_TABLE = browser-tool.tbl
);
const uiApi = uiDocument.defaultView.linuxCncIniPanelApi;
if (
!uiApi?.getMachineSessionControlView ||
!uiApi?.getMachineSessionStateBundle ||
!uiApi?.getMachineSessionStateReport ||
!uiApi?.getMachineSessionStateReportExport ||
@@ -830,6 +831,32 @@ TOOL_TABLE = browser-tool.tbl
machineSessionStateBundle.workflowStatus.lastAction,
"UI legacy machine session state bundle getter matches API namespace",
);
const machineSessionControlView = uiApi.getMachineSessionControlView();
assertEqual(
machineSessionControlView.status.runStatus,
"ok",
"UI machine session control view run status",
);
assertEqual(
machineSessionControlView.sections[0].id,
"readiness",
"UI machine session control view readiness section",
);
assertEqual(
machineSessionControlView.sections[1].rows[1].value,
"save-session-snapshot",
"UI machine session control view session workflow row",
);
assertEqual(
machineSessionControlView.sections[2].rows[1].value,
2,
"UI machine session control view canonical event row",
);
assertEqual(
uiDocument.defaultView.getMachineSessionControlView().status.lastAction,
machineSessionControlView.status.lastAction,
"UI legacy machine session control view getter matches API namespace",
);
assertEqual(
uiDocument.defaultView.getMachineSessionStateReport().status.lastAction,
machineSessionStateReport.status.lastAction,

View File

@@ -2,6 +2,7 @@ import assert from "node:assert/strict";
import {
createIniPanelStateSummary,
createMachineSessionControlView,
createMachineSessionStateSnapshot,
createMachineSessionStateBundle,
createMachineSessionStateReport,
@@ -473,17 +474,92 @@ assert.deepEqual(
);
assert.deepEqual(
createMachineSessionControlView({
report: expectedBundleReport,
export: {
filename: "machine-session-state-report.json",
mediaType: "application/json",
report: expectedBundleReport,
text: `${JSON.stringify(expectedBundleReport, null, 2)}\n`,
},
workflowStatus: {
readiness: {
ini: "INI WASM: ready",
interp: "Interpreter WASM: ready",
opfs: "OPFS: ready",
},
lastAction: "run-gcode",
error: null,
session: {
snapshotLabel: "ui-machine-session/ui-machine-session.json",
workflow: "save-session-snapshot",
ini: "/work/session-machine.ini",
parameters: null,
toolTable: null,
gcode: "linuxcnc/gcode/ui-session.ngc",
},
run: {
status: "ok",
canonicalEventCount: 2,
motionSnapshotCount: 2,
iniWasmPath: null,
},
},
}),
{
status: {
lastAction: "run-gcode",
error: null,
runStatus: "ok",
},
sections: [
{
id: "readiness",
title: "Readiness",
rows: [
{ label: "INI", value: "INI WASM: ready" },
{ label: "Interpreter", value: "Interpreter WASM: ready" },
{ label: "OPFS", value: "OPFS: ready" },
],
},
{
id: "session",
title: "Session",
rows: [
{ label: "Snapshot", value: "ui-machine-session/ui-machine-session.json" },
{ label: "Workflow", value: "save-session-snapshot" },
{ label: "INI", value: "/work/session-machine.ini" },
{ label: "G-code", value: "linuxcnc/gcode/ui-session.ngc" },
],
},
{
id: "run",
title: "Run",
rows: [
{ label: "Status", value: "ok" },
{ label: "Canonical events", value: 2 },
{ label: "Motion snapshots", value: 2 },
{ label: "INI WASM", value: null },
],
},
],
},
);
assert.deepEqual(
{
getMachineSessionControlView: typeof globalThis.window?.linuxCncIniPanelApi?.getMachineSessionControlView,
getMachineSessionStateBundle: typeof globalThis.window?.linuxCncIniPanelApi?.getMachineSessionStateBundle,
getMachineSessionStateReport: typeof globalThis.window?.linuxCncIniPanelApi?.getMachineSessionStateReport,
getMachineSessionStateReportExport: typeof globalThis.window?.linuxCncIniPanelApi?.getMachineSessionStateReportExport,
getMachineSessionWorkflowStatus: typeof globalThis.window?.linuxCncIniPanelApi?.getMachineSessionWorkflowStatus,
getMachineSessionStateBundle: typeof globalThis.window?.linuxCncIniPanelApi?.getMachineSessionStateBundle,
},
{
getMachineSessionControlView: "undefined",
getMachineSessionStateBundle: "undefined",
getMachineSessionStateReport: "undefined",
getMachineSessionStateReportExport: "undefined",
getMachineSessionWorkflowStatus: "undefined",
getMachineSessionStateBundle: "undefined",
},
);