推进 INI 面板 shell integration browser workflow smoke

This commit is contained in:
2026-06-15 07:54:19 +08:00
parent 1f9a8c33b0
commit a663c8dbca
5 changed files with 291 additions and 0 deletions

View File

@@ -1763,3 +1763,80 @@ host_wasm_opfs_browser_smokes=ok
继续推进 browser/UI workflow。下一批建议把 shell integration readiness 接到 control-page 或 launch 继续推进 browser/UI workflow。下一批建议把 shell integration readiness 接到 control-page 或 launch
的外部 shell smoke 中,形成“读取 integration manifest -> readiness gate -> 打开 control page -> 的外部 shell smoke 中,形成“读取 integration manifest -> readiness gate -> 打开 control page ->
读取 readonly status”的端到端只读接入 workflow仍保持只读不新增控制按钮不解析 G-code。 读取 readonly status”的端到端只读接入 workflow仍保持只读不新增控制按钮不解析 G-code。
二十二、2026-06-15 继续执行记录INI panel shell integration browser workflow smoke
本轮按“第一原则:加快实质性推进”继续推进 browser/UI workflow把 shell integration readiness
接入真实 browser 端到端只读外部 shell workflow覆盖“读取 integration manifest -> readiness gate
-> 打开 control page -> 读取 readonly status”的接入链路。
完成内容:
- 新增 `wasm-port/tests/browser/ini_panel_shell_integration_workflow_smoke.html`
- 该 browser smoke 模拟外部 shell
- 先加载真实 `launch.html`
- 读取 `linuxCncIniPanelLaunchApi.getShellIntegrationManifest()`
- 读取 `linuxCncIniPanelLaunchApi.getShellIntegrationReadiness()`
- 校验 readiness `phase === "ready"`、`ready === true`、`missing` 为空;
- 从 integration manifest 的 target pages 找到 control-page target
- 加载真实 `control-page.html`
- 校验 control-page browser API manifest 与 integration manifest 对齐;
- 等待 embedded INI panel API ready
- 通过既有 UI workflow 写入 INI、保存 machine files、load session
- 使用 `readControlPageStatus()` 读取 readonly machine-session status
- 校验 workflow last action、parameter file、tool table 与 control-page DOM status
- `verify_ini_panel_browser.sh` 纳入新 browser smoke并输出
`browser_ini_shell_integration_workflow_smoke=ok`
- `docs/panel-entry.md` 记录该 browser workflow smoke 的外部 shell handoff 目的;
- `verify_ini_panel_entry_docs.mjs` 校验:
- 文档记录新 smoke
- browser 聚合脚本执行新 smoke
- 新 smoke 使用 `getShellIntegrationReadiness()`
- 新 smoke 使用 `readControlPageStatus()`
- 新 smoke 输出成功标记;
- 未新增控制按钮;
- 未解析 G-code
- 未解释 canonical events
- 未新增 JS CNC 语义;
- 未改变 OPFS/session persistence、LinuxCNC interpreter、tool、parameter、planner 或
LinuxCNC-owned runtime semantics。
验证已通过:
```bash
git diff --check
wasm-port/tests/ui/node/verify_ini_panel_entry_docs.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_entry_docs_node_smoke=ok
ui_node_smokes=ok
browser_ini_opfs_smoke=ok
browser_ini_control_page_smoke=ok
browser_ini_launch_smoke=ok
browser_ini_shell_integration_workflow_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
```
下一步建议:
继续推进 SDK/API surface。下一批建议把 shell integration workflow 的只读 handoff 抽成 Node 可测
helper例如 `createIniPanelShellIntegrationWorkflowPlan()`),让外部 shell 在不开 browser 的情况下
获得 target page、readiness、required methods 与 readonly status handoff plan仍保持只读不新增
控制按钮,不解析 G-code。

View File

@@ -124,6 +124,10 @@ Use `createIniPanelShellIntegrationReadiness()` or
needs a read-only `ready`/`blocked` status, failed check names, API names, and needs a read-only `ready`/`blocked` status, failed check names, API names, and
counts for pages, methods, and persistence capabilities before mounting any counts for pages, methods, and persistence capabilities before mounting any
embedded control page. embedded control page.
The browser workflow smoke
`tests/browser/ini_panel_shell_integration_workflow_smoke.html` verifies the
same external shell handoff: read integration manifest, pass readiness gate,
open the control page target, then read readonly machine-session status.
The control-page refresh workflow lives in The control-page refresh workflow lives in
`runtime/ui/ini-panel/control-page-refresh-workflow.js`. Use `runtime/ui/ini-panel/control-page-refresh-workflow.js`. Use

View File

@@ -0,0 +1,181 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>LinuxCNC INI Shell Integration Workflow Smoke</title>
</head>
<body>
<pre id="status">running</pre>
<script type="module">
function assertEqual(actual, expected, label) {
if (actual !== expected) {
throw new Error(`${label}: expected ${expected}, got ${actual}`);
}
}
function delay(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
async function waitFor(predicate, label) {
const deadline = performance.now() + 10000;
while (performance.now() < deadline) {
const value = predicate();
if (value) {
return value;
}
await delay(50);
}
throw new Error(`Timed out waiting for ${label}`);
}
async function loadFrame(src) {
const frame = document.createElement("iframe");
frame.src = src;
document.body.appendChild(frame);
await new Promise((resolve, reject) => {
frame.addEventListener("load", resolve, { once: true });
frame.addEventListener("error", reject, { once: true });
});
return frame.contentDocument;
}
try {
const iniText = `[EMC]
MACHINE = browser-shell-integration
[TRAJ]
LINEAR_UNITS = mm
COORDINATES = X Y Z
[KINS]
KINEMATICS = trivkins
JOINTS = 3
[RS274NGC]
PARAMETER_FILE = browser-shell-linuxcnc.var
[EMCIO]
TOOL_TABLE = browser-shell-tool.tbl
`;
const launchDoc = await loadFrame("../../runtime/ui/ini-panel/launch.html");
const launchApi = launchDoc.defaultView.linuxCncIniPanelLaunchApi;
if (
!launchApi?.getShellIntegrationManifest ||
!launchApi?.getShellIntegrationReadiness
) {
throw new Error("launch API namespace did not expose shell integration helpers");
}
const integrationManifest = launchApi.getShellIntegrationManifest();
const readiness = launchApi.getShellIntegrationReadiness();
assertEqual(readiness.phase, "ready", "shell integration readiness phase");
assertEqual(readiness.ready, true, "shell integration readiness");
assertEqual(readiness.missing.join(","), "", "shell integration readiness missing checks");
assertEqual(readiness.apiNames.launch, "ini-panel-launch", "shell integration launch API name");
assertEqual(
readiness.apiNames.controlPage,
"ini-panel-control-page",
"shell integration control-page API name",
);
assertEqual(
integrationManifest.compatibility.launchApiManifest,
true,
"shell integration launch compatibility",
);
assertEqual(
integrationManifest.compatibility.controlPageApiManifest,
true,
"shell integration control-page compatibility",
);
const controlTarget = integrationManifest.launchApiManifest.targetPages.find(
(page) => page.entryId === "control-view",
);
assertEqual(controlTarget.href, "./control-page.html", "shell integration control target");
const controlDoc = await loadFrame(`../../runtime/ui/ini-panel/${controlTarget.href.slice(2)}`);
const controlPageApi = controlDoc.defaultView.linuxCncIniPanelControlPageApi;
if (
!controlPageApi?.getControlPageApiManifest ||
!controlPageApi?.readControlPageStatus ||
!controlPageApi?.loadControlPageSessionState
) {
throw new Error("control page API namespace did not expose readonly helpers");
}
assertEqual(
controlPageApi.getControlPageApiManifest().apiName,
integrationManifest.controlPageApiManifest.apiName,
"shell integration control manifest handoff",
);
const panelFrame = controlDoc.getElementById("panel-frame");
const panelDoc = await waitFor(
() => panelFrame?.contentDocument,
"embedded INI panel frame",
);
await waitFor(
() => panelDoc.defaultView?.linuxCncIniPanelApi?.getMachineSessionControlView?.(),
"embedded INI panel API",
);
assertEqual(
controlPageApi.loadControlPageSessionState().phase,
"ready",
"shell integration control-page session readiness",
);
assertEqual(
controlPageApi.readControlPageStatus().status.lastAction,
null,
"shell integration initial readonly status",
);
panelDoc.getElementById("ini-editor").value = iniText;
panelDoc.getElementById("query").click();
await waitFor(
() => panelDoc.getElementById("field-parameter-file")?.textContent.includes("browser-shell-linuxcnc.var"),
"shell integration query",
);
panelDoc.getElementById("save-machine-files").click();
await waitFor(
() => panelDoc.getElementById("log")?.textContent.includes("Saved machine text files"),
"shell integration save machine files",
);
panelDoc.getElementById("load-session").click();
await waitFor(
() => panelDoc.getElementById("log")?.textContent.includes("Loaded machine session"),
"shell integration load session",
);
const readonlyStatus = controlPageApi.readControlPageStatus();
assertEqual(
readonlyStatus.workflowStatus.lastAction,
"load-session",
"shell integration readonly workflow last action",
);
assertEqual(
readonlyStatus.session.parameterFile,
"browser-shell-linuxcnc.var",
"shell integration readonly session parameter file",
);
assertEqual(
readonlyStatus.stateBundle.report.readiness.toolTable,
"browser-shell-tool.tbl",
"shell integration readonly state bundle tool table",
);
assertEqual(
controlDoc.getElementById("control-page-status").textContent,
"load-session | not-run | -",
"shell integration control-page DOM status",
);
status.textContent = "browser_ini_shell_integration_workflow_smoke=ok";
console.log("browser_ini_shell_integration_workflow_smoke=ok");
} catch (error) {
status.textContent = `browser_ini_shell_integration_workflow_smoke=failed: ${error.message}`;
console.error(error);
throw error;
}
</script>
</body>
</html>

View File

@@ -63,9 +63,11 @@ PORT="$(cat "$PORT_FILE")"
URL="http://127.0.0.1:$PORT/tests/browser/ini_panel_smoke.html" URL="http://127.0.0.1:$PORT/tests/browser/ini_panel_smoke.html"
CONTROL_URL="http://127.0.0.1:$PORT/tests/browser/ini_panel_control_page_smoke.html" CONTROL_URL="http://127.0.0.1:$PORT/tests/browser/ini_panel_control_page_smoke.html"
LAUNCH_URL="http://127.0.0.1:$PORT/tests/browser/ini_panel_launch_smoke.html" LAUNCH_URL="http://127.0.0.1:$PORT/tests/browser/ini_panel_launch_smoke.html"
SHELL_INTEGRATION_URL="http://127.0.0.1:$PORT/tests/browser/ini_panel_shell_integration_workflow_smoke.html"
OUT="$TMP_DIR/chromium.out" OUT="$TMP_DIR/chromium.out"
CONTROL_OUT="$TMP_DIR/chromium-control.out" CONTROL_OUT="$TMP_DIR/chromium-control.out"
LAUNCH_OUT="$TMP_DIR/chromium-launch.out" LAUNCH_OUT="$TMP_DIR/chromium-launch.out"
SHELL_INTEGRATION_OUT="$TMP_DIR/chromium-shell-integration.out"
"$CHROMIUM" \ "$CHROMIUM" \
--headless=new \ --headless=new \
@@ -117,3 +119,20 @@ if ! grep -Fq "browser_ini_launch_smoke=ok" "$LAUNCH_OUT"; then
fi fi
echo "browser_ini_launch_smoke=ok" echo "browser_ini_launch_smoke=ok"
"$CHROMIUM" \
--headless=new \
--disable-gpu \
--no-sandbox \
--user-data-dir="$CHROME_PROFILE" \
--virtual-time-budget=10000 \
--dump-dom \
"$SHELL_INTEGRATION_URL" >"$SHELL_INTEGRATION_OUT" 2>&1
if ! grep -Fq "browser_ini_shell_integration_workflow_smoke=ok" "$SHELL_INTEGRATION_OUT"; then
echo "browser INI shell integration workflow smoke failed" >&2
sed -n '1,220p' "$SHELL_INTEGRATION_OUT" >&2
exit 1
fi
echo "browser_ini_shell_integration_workflow_smoke=ok"

View File

@@ -35,6 +35,11 @@ const docText = readFileSync(resolve(root, "docs/panel-entry.md"), "utf8");
const shellText = readFileSync(resolve(root, "runtime/ui/ini-panel/ui-shell.js"), "utf8"); const shellText = readFileSync(resolve(root, "runtime/ui/ini-panel/ui-shell.js"), "utf8");
const controlPageText = readFileSync(resolve(root, "runtime/ui/ini-panel/control-page.js"), "utf8"); const controlPageText = readFileSync(resolve(root, "runtime/ui/ini-panel/control-page.js"), "utf8");
const launchText = readFileSync(resolve(root, "runtime/ui/ini-panel/launch.html"), "utf8"); const launchText = readFileSync(resolve(root, "runtime/ui/ini-panel/launch.html"), "utf8");
const browserIniPanelText = readFileSync(resolve(root, "tests/browser/verify_ini_panel_browser.sh"), "utf8");
const shellIntegrationWorkflowSmokeText = readFileSync(
resolve(root, "tests/browser/ini_panel_shell_integration_workflow_smoke.html"),
"utf8",
);
const refreshWorkflowText = readFileSync( const refreshWorkflowText = readFileSync(
resolve(root, "runtime/ui/ini-panel/control-page-refresh-workflow.js"), resolve(root, "runtime/ui/ini-panel/control-page-refresh-workflow.js"),
"utf8", "utf8",
@@ -222,6 +227,11 @@ assert.match(docText, /\blinuxCncIniPanelLaunchApi\.getShellIntegrationSchema\b/
assert.match(docText, /\blinuxCncIniPanelLaunchApi\.getShellIntegrationManifest\b/); assert.match(docText, /\blinuxCncIniPanelLaunchApi\.getShellIntegrationManifest\b/);
assert.match(docText, /\blinuxCncIniPanelLaunchApi\.getShellIntegrationReadiness\b/); assert.match(docText, /\blinuxCncIniPanelLaunchApi\.getShellIntegrationReadiness\b/);
assert.match(docText, /\blinuxCncIniPanelLaunchApi\.getControlPageApiMethods\b/); assert.match(docText, /\blinuxCncIniPanelLaunchApi\.getControlPageApiMethods\b/);
assert.match(docText, /\bini_panel_shell_integration_workflow_smoke\.html\b/);
assert.match(browserIniPanelText, /\bini_panel_shell_integration_workflow_smoke\.html\b/);
assert.match(shellIntegrationWorkflowSmokeText, /\bgetShellIntegrationReadiness\b/);
assert.match(shellIntegrationWorkflowSmokeText, /\breadControlPageStatus\b/);
assert.match(shellIntegrationWorkflowSmokeText, /\bbrowser_ini_shell_integration_workflow_smoke=ok\b/);
assert.match(docText, /\bOPFS\/session persistence capabilities\b/); assert.match(docText, /\bOPFS\/session persistence capabilities\b/);
assert.match(docText, /\bmachine files\b/); assert.match(docText, /\bmachine files\b/);
assert.match(docText, /\bsession snapshots\b/); assert.match(docText, /\bsession snapshots\b/);