推进 INI 面板 workflow overview embedding contract

This commit is contained in:
2026-06-15 17:51:21 +08:00
parent b61e7e980d
commit 766f630dfb
11 changed files with 442 additions and 5 deletions

View File

@@ -1113,3 +1113,95 @@ host_wasm_opfs_browser_smokes=ok
`createIniPanelShellSessionHandoffPackageDisplayViewModel()` 或等价 helper把 package report `createIniPanelShellSessionHandoffPackageDisplayViewModel()` 或等价 helper把 package report
压成稳定 label/value rows 与 status text供 launch 页面或外部 shell 直接渲染 package report 压成稳定 label/value rows 与 status text供 launch 页面或外部 shell 直接渲染 package report
仍保持只读,不新增控制按钮,不解析 G-code。 仍保持只读,不新增控制按钮,不解析 G-code。
十、2026-06-15 继续执行记录INI panel workflow overview embedding contract
本轮按 `text13.txt` 执行,不再扩展 `text12.txt`。继续推进 browser/UI workflow
新增只读 workflow-overview embedding contract让外部 shell 可以先验证
`workflow-overview.html` iframe 的 API shape再用 launch API 挂载 workflow overview。
完成内容:
- `ui-shell.js` 新增:
- `createIniPanelShellWorkflowOverviewContract()`
- `createIniPanelShellWorkflowOverviewReadiness()`
- `mountIniPanelShellWorkflowOverviewEmbedding()`
- `createIniPanelLaunchApiManifest().methods` 新增:
- `getWorkflowOverviewContract`
- `getWorkflowOverviewReadiness`
- `mountWorkflowOverviewEmbedding`
- `launch.html` 的 `window.linuxCncIniPanelLaunchApi` 新增真实 browser 入口:
- `getWorkflowOverviewContract()`
- `getWorkflowOverviewReadiness()`
- `mountWorkflowOverviewEmbedding()`
- `workflow-overview.html` 的 `window.linuxCncIniPanelWorkflowOverviewApi` 新增自描述入口:
- `getWorkflowOverviewContract()`
- `getWorkflowOverviewReadinessFromApi()`
- `mountWorkflowOverviewEmbedding()`
- `verify_ini_panel_ui_shell.mjs` 覆盖:
- overview contract shape
- missing API readiness
- ready API readiness
- embedding result
- thrown mount error 转成 data error
- `verify_ini_panel_launch_api_manifest.mjs` 覆盖 launch manifest 与 launch 页面真实 API
- `verify_ini_panel_shell_integration_manifest.mjs` 覆盖 overview contract/readiness helper
- `ini_panel_launch_smoke.html` 校验真实 launch iframe 加载真实 workflow-overview iframe 后,
能读取 readiness 并执行 embedding workflow
- `ini_panel_shell_integration_workflow_smoke.html` 校验外部 shell workflow 可通过 launch API
消费真实 workflow-overview iframe
- `docs/panel-entry.md` 与 `verify_ini_panel_entry_docs.mjs` 同步记录新 helper/API
- 未新增控制按钮;
- 未执行 action-plan
- 未解析 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_ui_shell.sh
wasm-port/tests/ui/node/verify_ini_panel_launch_api_manifest.sh
wasm-port/tests/ui/node/verify_ini_panel_shell_integration_manifest.sh
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_ui_shell_node_smoke=ok
ini_panel_launch_api_manifest_node_smoke=ok
ini_panel_shell_integration_manifest_node_smoke=ok
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_workflow_overview_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
```
下一步建议:
继续推进 browser/UI workflow 与可执行 gate。下一批建议新增一个只读
`createIniPanelShellWorkflowOverviewEmbeddingReport()` 或等价 helper把 overview iframe
readiness、missing methods、mount phase、render row count 和 error 合并成 display-ready report
便于外部 shell 在不能嵌入 overview 时直接显示原因;仍保持只读,不新增控制按钮,不解析 G-code。

View File

@@ -39,6 +39,9 @@ import {
createIniPanelShellSessionHandoffWorkflowRenderState, createIniPanelShellSessionHandoffWorkflowRenderState,
createIniPanelShellSessionHandoffWorkflowDomContract, createIniPanelShellSessionHandoffWorkflowDomContract,
createIniPanelShellSessionHandoffWorkflowDomReadiness, createIniPanelShellSessionHandoffWorkflowDomReadiness,
createIniPanelShellWorkflowOverviewContract,
createIniPanelShellWorkflowOverviewReadiness,
mountIniPanelShellWorkflowOverviewEmbedding,
renderIniPanelShellSessionHandoffWorkflowState, renderIniPanelShellSessionHandoffWorkflowState,
mountIniPanelShellSessionHandoffWorkflow, mountIniPanelShellSessionHandoffWorkflow,
renderIniPanelShellSessionHandoffMountState, renderIniPanelShellSessionHandoffMountState,
@@ -286,6 +289,21 @@ Use `mountIniPanelShellSessionHandoffWorkflow()` or
`linuxCncIniPanelLaunchApi.mountShellSessionHandoffWorkflow()` when an outer `linuxCncIniPanelLaunchApi.mountShellSessionHandoffWorkflow()` when an outer
shell needs one structured workflow DOM mount result containing contract, shell needs one structured workflow DOM mount result containing contract,
readiness, render-state, render result, and any mount error as data. Use readiness, render-state, render result, and any mount error as data. Use
`createIniPanelShellWorkflowOverviewContract()` or
`linuxCncIniPanelLaunchApi.getWorkflowOverviewContract()` when an outer shell
needs the `workflow-overview.html` page href, browser API namespace, and method
names before embedding that page. Use
`createIniPanelShellWorkflowOverviewReadiness()` or
`linuxCncIniPanelLaunchApi.getWorkflowOverviewReadiness()` to check whether a
loaded workflow overview iframe exposes the expected read-only API. Use
`mountIniPanelShellWorkflowOverviewEmbedding()` or
`linuxCncIniPanelLaunchApi.mountWorkflowOverviewEmbedding()` when an outer
shell needs one structured embedding result containing contract readiness,
render-state, workflow overview mount result, and any mount error as data. The
workflow overview page also exposes
`linuxCncIniPanelWorkflowOverviewApi.mountWorkflowOverviewEmbedding()` for the
same read-only embedding path from inside the iframe.
Use
`renderIniPanelShellSessionHandoffMountState()` or `renderIniPanelShellSessionHandoffMountState()` or
`linuxCncIniPanelLaunchApi.renderShellSessionHandoffMountState()` when an outer `linuxCncIniPanelLaunchApi.renderShellSessionHandoffMountState()` when an outer
shell needs the shared read-only DOM renderer for those rows. The launch page shell needs the shared read-only DOM renderer for those rows. The launch page

View File

@@ -270,6 +270,15 @@
documentRef: document, documentRef: document,
}) })
), ),
getWorkflowOverviewContract: (options) => (
shellViewModel.createWorkflowOverviewContract(options)
),
getWorkflowOverviewReadiness: (options) => (
shellViewModel.createWorkflowOverviewReadiness(options)
),
mountWorkflowOverviewEmbedding: (options) => (
shellViewModel.mountWorkflowOverviewEmbedding(options)
),
isSupportedShellSessionHandoffPackage: shellViewModel.isSupportedShellSessionHandoffPackage, isSupportedShellSessionHandoffPackage: shellViewModel.isSupportedShellSessionHandoffPackage,
getLauncherLinks: () => shellViewModel.launcherLinks, getLauncherLinks: () => shellViewModel.launcherLinks,
getControlPageApiMethods: () => shellViewModel.controlPage.browserApiMethods, getControlPageApiMethods: () => shellViewModel.controlPage.browserApiMethods,

View File

@@ -156,6 +156,9 @@ export function createIniPanelLaunchApiManifest(entries = getVisibleIniPanelEntr
"getShellSessionHandoffWorkflowDomReadiness", "getShellSessionHandoffWorkflowDomReadiness",
"renderShellSessionHandoffWorkflowState", "renderShellSessionHandoffWorkflowState",
"mountShellSessionHandoffWorkflow", "mountShellSessionHandoffWorkflow",
"getWorkflowOverviewContract",
"getWorkflowOverviewReadiness",
"mountWorkflowOverviewEmbedding",
"isSupportedShellSessionHandoffPackage", "isSupportedShellSessionHandoffPackage",
"getLauncherLinks", "getLauncherLinks",
"getControlPageApiMethods", "getControlPageApiMethods",
@@ -1559,6 +1562,41 @@ export function createIniPanelShellSessionHandoffWorkflowDomContract({
}; };
} }
export function createIniPanelShellWorkflowOverviewContract() {
return {
pageHref: "./workflow-overview.html",
entryId: "workflow-overview",
apiName: "linuxCncIniPanelWorkflowOverviewApi",
methodNames: [
"getWorkflowOverviewRenderState",
"getWorkflowOverviewDisplayViewModel",
"getWorkflowOverviewSummary",
"getWorkflowOverviewDomContract",
"getWorkflowOverviewDomReadiness",
"renderWorkflowOverview",
"mountWorkflowOverview",
"createWorkflowOverviewRenderState",
],
};
}
export function createIniPanelShellWorkflowOverviewReadiness({
overviewApi = null,
contract = createIniPanelShellWorkflowOverviewContract(),
} = {}) {
const methodNames = Array.isArray(contract?.methodNames) ? contract.methodNames : [];
const missing = methodNames.filter((methodName) => typeof overviewApi?.[methodName] !== "function");
const ready = missing.length === 0;
return {
apiName: "ini-panel-shell-workflow-overview-readiness",
readinessVersion: 1,
phase: ready ? "ready" : "blocked",
ready,
missing,
contract,
};
}
export function createIniPanelShellSessionHandoffWorkflowDomReadiness({ export function createIniPanelShellSessionHandoffWorkflowDomReadiness({
rowDatasetPrefix = "handoffWorkflow", rowDatasetPrefix = "handoffWorkflow",
contract = createIniPanelShellSessionHandoffWorkflowDomContract({ rowDatasetPrefix }), contract = createIniPanelShellSessionHandoffWorkflowDomContract({ rowDatasetPrefix }),
@@ -1714,6 +1752,61 @@ export function mountIniPanelShellSessionHandoffWorkflow({
} }
} }
export function mountIniPanelShellWorkflowOverviewEmbedding({
overviewApi = null,
contract = createIniPanelShellWorkflowOverviewContract(),
mountOptions = {},
} = {}) {
const readiness = createIniPanelShellWorkflowOverviewReadiness({
overviewApi,
contract,
});
if (!readiness.ready) {
return {
apiName: "ini-panel-shell-workflow-overview-embedding",
embeddingVersion: 1,
phase: "blocked",
ready: false,
pageHref: contract.pageHref,
contract,
readiness,
renderState: null,
mountResult: null,
error: null,
};
}
try {
const renderState = overviewApi.getWorkflowOverviewRenderState();
const mountResult = overviewApi.mountWorkflowOverview(mountOptions);
return {
apiName: "ini-panel-shell-workflow-overview-embedding",
embeddingVersion: 1,
phase: mountResult?.phase ?? "blocked",
ready: mountResult?.ready === true,
pageHref: contract.pageHref,
contract,
readiness,
renderState,
mountResult,
error: null,
};
} catch (error) {
return {
apiName: "ini-panel-shell-workflow-overview-embedding",
embeddingVersion: 1,
phase: "blocked",
ready: false,
pageHref: contract.pageHref,
contract,
readiness,
renderState: null,
mountResult: null,
error: error?.message ?? String(error),
};
}
}
export function createIniPanelShellViewModel(entries = getVisibleIniPanelEntries()) { export function createIniPanelShellViewModel(entries = getVisibleIniPanelEntries()) {
const shellIntegrationManifest = createIniPanelShellIntegrationManifest(entries); const shellIntegrationManifest = createIniPanelShellIntegrationManifest(entries);
const shellIntegrationReadiness = createIniPanelShellIntegrationReadiness(shellIntegrationManifest); const shellIntegrationReadiness = createIniPanelShellIntegrationReadiness(shellIntegrationManifest);
@@ -1800,6 +1893,7 @@ export function createIniPanelShellViewModel(entries = getVisibleIniPanelEntries
shellSessionHandoffWorkflowDisplayViewModel, shellSessionHandoffWorkflowDisplayViewModel,
); );
const shellSessionHandoffWorkflowDomContract = createIniPanelShellSessionHandoffWorkflowDomContract(); const shellSessionHandoffWorkflowDomContract = createIniPanelShellSessionHandoffWorkflowDomContract();
const shellWorkflowOverviewContract = createIniPanelShellWorkflowOverviewContract();
return { return {
pages: entries.map(({ id, href, title }) => ({ pages: entries.map(({ id, href, title }) => ({
id, id,
@@ -1832,6 +1926,7 @@ export function createIniPanelShellViewModel(entries = getVisibleIniPanelEntries
shellSessionHandoffWorkflowDisplayViewModel, shellSessionHandoffWorkflowDisplayViewModel,
shellSessionHandoffWorkflowRenderState, shellSessionHandoffWorkflowRenderState,
shellSessionHandoffWorkflowDomContract, shellSessionHandoffWorkflowDomContract,
shellWorkflowOverviewContract,
launchApiSchema: createIniPanelLaunchApiSchema(), launchApiSchema: createIniPanelLaunchApiSchema(),
launchApiManifest: createIniPanelLaunchApiManifest(entries), launchApiManifest: createIniPanelLaunchApiManifest(entries),
isSupportedShellIntegrationManifest: isSupportedIniPanelShellIntegrationManifest, isSupportedShellIntegrationManifest: isSupportedIniPanelShellIntegrationManifest,
@@ -1867,6 +1962,9 @@ export function createIniPanelShellViewModel(entries = getVisibleIniPanelEntries
createShellSessionHandoffWorkflowDomReadiness: createIniPanelShellSessionHandoffWorkflowDomReadiness, createShellSessionHandoffWorkflowDomReadiness: createIniPanelShellSessionHandoffWorkflowDomReadiness,
renderShellSessionHandoffWorkflowState: renderIniPanelShellSessionHandoffWorkflowState, renderShellSessionHandoffWorkflowState: renderIniPanelShellSessionHandoffWorkflowState,
mountShellSessionHandoffWorkflow: mountIniPanelShellSessionHandoffWorkflow, mountShellSessionHandoffWorkflow: mountIniPanelShellSessionHandoffWorkflow,
createWorkflowOverviewContract: createIniPanelShellWorkflowOverviewContract,
createWorkflowOverviewReadiness: createIniPanelShellWorkflowOverviewReadiness,
mountWorkflowOverviewEmbedding: mountIniPanelShellWorkflowOverviewEmbedding,
isSupportedShellSessionHandoffPackage: isSupportedIniPanelShellSessionHandoffPackage, isSupportedShellSessionHandoffPackage: isSupportedIniPanelShellSessionHandoffPackage,
controlPage: { controlPage: {
browserApiSchema: createControlPageBrowserApiSchema(), browserApiSchema: createControlPageBrowserApiSchema(),

View File

@@ -87,6 +87,9 @@
createIniPanelShellSessionHandoffWorkflowDomContract, createIniPanelShellSessionHandoffWorkflowDomContract,
createIniPanelShellSessionHandoffWorkflowDomReadiness, createIniPanelShellSessionHandoffWorkflowDomReadiness,
createIniPanelShellSessionHandoffWorkflowRenderState, createIniPanelShellSessionHandoffWorkflowRenderState,
createIniPanelShellWorkflowOverviewContract,
createIniPanelShellWorkflowOverviewReadiness,
mountIniPanelShellWorkflowOverviewEmbedding,
renderIniPanelShellSessionHandoffWorkflowState, renderIniPanelShellSessionHandoffWorkflowState,
mountIniPanelShellSessionHandoffWorkflow, mountIniPanelShellSessionHandoffWorkflow,
} from "./ui-shell.js"; } from "./ui-shell.js";
@@ -118,6 +121,16 @@
}) })
), ),
createWorkflowOverviewRenderState: createIniPanelShellSessionHandoffWorkflowRenderState, createWorkflowOverviewRenderState: createIniPanelShellSessionHandoffWorkflowRenderState,
getWorkflowOverviewContract: () => createIniPanelShellWorkflowOverviewContract(),
getWorkflowOverviewReadinessFromApi: () => createIniPanelShellWorkflowOverviewReadiness({
overviewApi: window.linuxCncIniPanelWorkflowOverviewApi,
}),
mountWorkflowOverviewEmbedding: (options) => (
mountIniPanelShellWorkflowOverviewEmbedding({
overviewApi: window.linuxCncIniPanelWorkflowOverviewApi,
...(options ?? {}),
})
),
}; };
</script> </script>
</body> </body>

View File

@@ -23,9 +23,9 @@
} }
} }
async function loadLaunchFrame() { async function loadFrame(src) {
const frame = document.createElement("iframe"); const frame = document.createElement("iframe");
frame.src = "../../runtime/ui/ini-panel/launch.html"; frame.src = src;
document.body.appendChild(frame); document.body.appendChild(frame);
await new Promise((resolve, reject) => { await new Promise((resolve, reject) => {
frame.addEventListener("load", resolve, { once: true }); frame.addEventListener("load", resolve, { once: true });
@@ -34,6 +34,10 @@
return frame.contentDocument; return frame.contentDocument;
} }
async function loadLaunchFrame() {
return loadFrame("../../runtime/ui/ini-panel/launch.html");
}
try { try {
const visibleEntries = getVisibleIniPanelEntries(); const visibleEntries = getVisibleIniPanelEntries();
assertEqual(visibleEntries.length, 3, "launch visible entry length"); assertEqual(visibleEntries.length, 3, "launch visible entry length");
@@ -96,6 +100,9 @@
!launchApi?.getShellSessionHandoffPackageMountDisplayViewModel || !launchApi?.getShellSessionHandoffPackageMountDisplayViewModel ||
!launchApi?.getShellSessionHandoffPackageMountRenderState || !launchApi?.getShellSessionHandoffPackageMountRenderState ||
!launchApi?.renderShellSessionHandoffMountState || !launchApi?.renderShellSessionHandoffMountState ||
!launchApi?.getWorkflowOverviewContract ||
!launchApi?.getWorkflowOverviewReadiness ||
!launchApi?.mountWorkflowOverviewEmbedding ||
!launchApi?.isSupportedShellSessionHandoffPackage || !launchApi?.isSupportedShellSessionHandoffPackage ||
!launchApi?.getLauncherLinks || !launchApi?.getLauncherLinks ||
!launchApi?.getControlPageApiMethods !launchApi?.getControlPageApiMethods
@@ -158,6 +165,7 @@
const shellSessionHandoffPackageMountState = launchApi.getShellSessionHandoffPackageMountState(); const shellSessionHandoffPackageMountState = launchApi.getShellSessionHandoffPackageMountState();
const shellSessionHandoffPackageMountDisplayViewModel = launchApi.getShellSessionHandoffPackageMountDisplayViewModel(); const shellSessionHandoffPackageMountDisplayViewModel = launchApi.getShellSessionHandoffPackageMountDisplayViewModel();
const shellSessionHandoffPackageMountRenderState = launchApi.getShellSessionHandoffPackageMountRenderState(); const shellSessionHandoffPackageMountRenderState = launchApi.getShellSessionHandoffPackageMountRenderState();
const workflowOverviewContract = launchApi.getWorkflowOverviewContract();
assertEqual( assertEqual(
shellIntegrationSchema.manifestVersion, shellIntegrationSchema.manifestVersion,
1, 1,
@@ -403,6 +411,21 @@
"Blocked: session-load-state", "Blocked: session-load-state",
"shell session handoff package mount render-state status", "shell session handoff package mount render-state status",
); );
assertEqual(
workflowOverviewContract.pageHref,
"./workflow-overview.html",
"workflow overview contract page",
);
assertEqual(
workflowOverviewContract.apiName,
"linuxCncIniPanelWorkflowOverviewApi",
"workflow overview contract API name",
);
assertEqual(
workflowOverviewContract.methodNames.includes("mountWorkflowOverview"),
true,
"workflow overview contract mount method",
);
assertEqual( assertEqual(
shellSessionHandoffPackage.ready, shellSessionHandoffPackage.ready,
true, true,
@@ -598,6 +621,32 @@
createControlPageBrowserApiMethods().join(","), createControlPageBrowserApiMethods().join(","),
"launch API control-page browser methods", "launch API control-page browser methods",
); );
const workflowOverviewDoc = await loadFrame("../../runtime/ui/ini-panel/workflow-overview.html");
const workflowOverviewApi = workflowOverviewDoc.defaultView.linuxCncIniPanelWorkflowOverviewApi;
if (
!workflowOverviewApi?.getWorkflowOverviewRenderState ||
!workflowOverviewApi?.getWorkflowOverviewContract ||
!workflowOverviewApi?.getWorkflowOverviewReadinessFromApi ||
!workflowOverviewApi?.mountWorkflowOverviewEmbedding
) {
throw new Error("workflow overview API namespace did not expose shell helpers");
}
const workflowOverviewReadiness = launchApi.getWorkflowOverviewReadiness({
overviewApi: workflowOverviewApi,
});
const workflowOverviewEmbedding = launchApi.mountWorkflowOverviewEmbedding({
overviewApi: workflowOverviewApi,
});
assertEqual(workflowOverviewApi.getWorkflowOverviewContract().pageHref, "./workflow-overview.html", "workflow overview page contract");
assertEqual(workflowOverviewReadiness.ready, true, "workflow overview readiness");
assertEqual(workflowOverviewReadiness.missing.join(","), "", "workflow overview readiness missing");
assertEqual(workflowOverviewApi.getWorkflowOverviewReadinessFromApi().ready, true, "workflow overview self readiness");
assertEqual(workflowOverviewEmbedding.apiName, "ini-panel-shell-workflow-overview-embedding", "workflow overview embedding API name");
assertEqual(workflowOverviewEmbedding.pageHref, "./workflow-overview.html", "workflow overview embedding page");
assertEqual(workflowOverviewEmbedding.ready, false, "workflow overview embedding default readiness");
assertEqual(workflowOverviewEmbedding.renderState.dataset.handoffScope, "workflow", "workflow overview embedding scope");
assertEqual(workflowOverviewEmbedding.mountResult.renderResult.rowCount, 5, "workflow overview embedding rows");
assertEqual(workflowOverviewEmbedding.error, null, "workflow overview embedding error");
status.textContent = "browser_ini_launch_smoke=ok"; status.textContent = "browser_ini_launch_smoke=ok";
console.log("browser_ini_launch_smoke=ok"); console.log("browser_ini_launch_smoke=ok");
} catch (error) { } catch (error) {

View File

@@ -102,12 +102,16 @@ TOOL_TABLE = browser-shell-tool.tbl
!launchApi?.getShellSessionHandoffWorkflowDomReadiness || !launchApi?.getShellSessionHandoffWorkflowDomReadiness ||
!launchApi?.renderShellSessionHandoffWorkflowState || !launchApi?.renderShellSessionHandoffWorkflowState ||
!launchApi?.mountShellSessionHandoffWorkflow || !launchApi?.mountShellSessionHandoffWorkflow ||
!launchApi?.getWorkflowOverviewContract ||
!launchApi?.getWorkflowOverviewReadiness ||
!launchApi?.mountWorkflowOverviewEmbedding ||
!launchApi?.isSupportedShellSessionHandoffPackage !launchApi?.isSupportedShellSessionHandoffPackage
) { ) {
throw new Error("launch API namespace did not expose shell integration helpers"); throw new Error("launch API namespace did not expose shell integration helpers");
} }
const integrationManifest = launchApi.getShellIntegrationManifest(); const integrationManifest = launchApi.getShellIntegrationManifest();
const workflowOverviewContract = launchApi.getWorkflowOverviewContract();
const readiness = launchApi.getShellIntegrationReadiness(); const readiness = launchApi.getShellIntegrationReadiness();
const handoffSummarySchema = launchApi.getShellSessionHandoffSummarySchema(); const handoffSummarySchema = launchApi.getShellSessionHandoffSummarySchema();
const handoffSummary = launchApi.getShellSessionHandoffSummary(); const handoffSummary = launchApi.getShellSessionHandoffSummary();
@@ -264,12 +268,32 @@ TOOL_TABLE = browser-shell-tool.tbl
true, true,
"shell integration control-page compatibility", "shell integration control-page compatibility",
); );
assertEqual(workflowOverviewContract.pageHref, "./workflow-overview.html", "shell workflow overview contract page");
assertEqual(
workflowOverviewContract.methodNames.includes("getWorkflowOverviewRenderState"),
true,
"shell workflow overview contract render method",
);
const controlTarget = integrationManifest.launchApiManifest.targetPages.find( const controlTarget = integrationManifest.launchApiManifest.targetPages.find(
(page) => page.entryId === "control-view", (page) => page.entryId === "control-view",
); );
assertEqual(controlTarget.href, "./control-page.html", "shell integration control target"); assertEqual(controlTarget.href, "./control-page.html", "shell integration control target");
assertEqual(handoffSummary.targetPage.href, controlTarget.href, "shell handoff summary target"); assertEqual(handoffSummary.targetPage.href, controlTarget.href, "shell handoff summary target");
const workflowOverviewDoc = await loadFrame(`../../runtime/ui/ini-panel/${workflowOverviewContract.pageHref.slice(2)}`);
const workflowOverviewApi = workflowOverviewDoc.defaultView.linuxCncIniPanelWorkflowOverviewApi;
const workflowOverviewReadiness = launchApi.getWorkflowOverviewReadiness({
overviewApi: workflowOverviewApi,
});
const workflowOverviewEmbedding = launchApi.mountWorkflowOverviewEmbedding({
overviewApi: workflowOverviewApi,
});
assertEqual(workflowOverviewReadiness.ready, true, "shell workflow overview iframe readiness");
assertEqual(workflowOverviewReadiness.missing.join(","), "", "shell workflow overview iframe missing methods");
assertEqual(workflowOverviewEmbedding.apiName, "ini-panel-shell-workflow-overview-embedding", "shell workflow overview embedding API");
assertEqual(workflowOverviewEmbedding.renderState.dataset.handoffScope, "workflow", "shell workflow overview embedding scope");
assertEqual(workflowOverviewEmbedding.mountResult.renderResult.rowCount, 5, "shell workflow overview embedding rows");
assertEqual(workflowOverviewEmbedding.error, null, "shell workflow overview embedding error");
const controlDoc = await loadFrame(`../../runtime/ui/ini-panel/${controlTarget.href.slice(2)}`); const controlDoc = await loadFrame(`../../runtime/ui/ini-panel/${controlTarget.href.slice(2)}`);
const controlPageApi = controlDoc.defaultView.linuxCncIniPanelControlPageApi; const controlPageApi = controlDoc.defaultView.linuxCncIniPanelControlPageApi;

View File

@@ -37,6 +37,9 @@ import {
createIniPanelShellSessionHandoffWorkflowRenderState, createIniPanelShellSessionHandoffWorkflowRenderState,
createIniPanelShellSessionHandoffWorkflowDomContract, createIniPanelShellSessionHandoffWorkflowDomContract,
createIniPanelShellSessionHandoffWorkflowDomReadiness, createIniPanelShellSessionHandoffWorkflowDomReadiness,
createIniPanelShellWorkflowOverviewContract,
createIniPanelShellWorkflowOverviewReadiness,
mountIniPanelShellWorkflowOverviewEmbedding,
renderIniPanelShellSessionHandoffWorkflowState, renderIniPanelShellSessionHandoffWorkflowState,
mountIniPanelShellSessionHandoffWorkflow, mountIniPanelShellSessionHandoffWorkflow,
renderIniPanelShellSessionHandoffMountState, renderIniPanelShellSessionHandoffMountState,
@@ -115,6 +118,9 @@ const requiredShellExports = [
"createIniPanelShellSessionHandoffWorkflowRenderState", "createIniPanelShellSessionHandoffWorkflowRenderState",
"createIniPanelShellSessionHandoffWorkflowDomContract", "createIniPanelShellSessionHandoffWorkflowDomContract",
"createIniPanelShellSessionHandoffWorkflowDomReadiness", "createIniPanelShellSessionHandoffWorkflowDomReadiness",
"createIniPanelShellWorkflowOverviewContract",
"createIniPanelShellWorkflowOverviewReadiness",
"mountIniPanelShellWorkflowOverviewEmbedding",
"renderIniPanelShellSessionHandoffWorkflowState", "renderIniPanelShellSessionHandoffWorkflowState",
"mountIniPanelShellSessionHandoffWorkflow", "mountIniPanelShellSessionHandoffWorkflow",
"renderIniPanelShellSessionHandoffMountState", "renderIniPanelShellSessionHandoffMountState",

View File

@@ -76,6 +76,9 @@ assert.deepEqual(manifest, {
"getShellSessionHandoffWorkflowDomReadiness", "getShellSessionHandoffWorkflowDomReadiness",
"renderShellSessionHandoffWorkflowState", "renderShellSessionHandoffWorkflowState",
"mountShellSessionHandoffWorkflow", "mountShellSessionHandoffWorkflow",
"getWorkflowOverviewContract",
"getWorkflowOverviewReadiness",
"mountWorkflowOverviewEmbedding",
"isSupportedShellSessionHandoffPackage", "isSupportedShellSessionHandoffPackage",
"getLauncherLinks", "getLauncherLinks",
"getControlPageApiMethods", "getControlPageApiMethods",
@@ -216,6 +219,9 @@ assert.match(launchText, /\bgetShellSessionHandoffWorkflowDomContract\b/);
assert.match(launchText, /\bgetShellSessionHandoffWorkflowDomReadiness\b/); assert.match(launchText, /\bgetShellSessionHandoffWorkflowDomReadiness\b/);
assert.match(launchText, /\brenderShellSessionHandoffWorkflowState\b/); assert.match(launchText, /\brenderShellSessionHandoffWorkflowState\b/);
assert.match(launchText, /\bmountShellSessionHandoffWorkflow\b/); assert.match(launchText, /\bmountShellSessionHandoffWorkflow\b/);
assert.match(launchText, /\bgetWorkflowOverviewContract\b/);
assert.match(launchText, /\bgetWorkflowOverviewReadiness\b/);
assert.match(launchText, /\bmountWorkflowOverviewEmbedding\b/);
assert.match(launchText, /\bisSupportedShellSessionHandoffPackage\b/); assert.match(launchText, /\bisSupportedShellSessionHandoffPackage\b/);
assert.match(launchText, /\bdata-handoff-workflow\b/); assert.match(launchText, /\bdata-handoff-workflow\b/);
assert.match(launchText, /\bdata-handoff-workflow-status\b/); assert.match(launchText, /\bdata-handoff-workflow-status\b/);

View File

@@ -33,6 +33,9 @@ import {
createIniPanelShellSessionHandoffWorkflowRenderState, createIniPanelShellSessionHandoffWorkflowRenderState,
createIniPanelShellSessionHandoffWorkflowDomContract, createIniPanelShellSessionHandoffWorkflowDomContract,
createIniPanelShellSessionHandoffWorkflowDomReadiness, createIniPanelShellSessionHandoffWorkflowDomReadiness,
createIniPanelShellWorkflowOverviewContract,
createIniPanelShellWorkflowOverviewReadiness,
mountIniPanelShellWorkflowOverviewEmbedding,
renderIniPanelShellSessionHandoffWorkflowState, renderIniPanelShellSessionHandoffWorkflowState,
mountIniPanelShellSessionHandoffWorkflow, mountIniPanelShellSessionHandoffWorkflow,
createIniPanelShellSessionHandoffPackageReport, createIniPanelShellSessionHandoffPackageReport,
@@ -161,7 +164,7 @@ assert.deepEqual(readiness, {
counts: { counts: {
pages: 3, pages: 3,
launcherLinks: 3, launcherLinks: 3,
launchMethods: 39, launchMethods: 42,
controlPageMethods: 11, controlPageMethods: 11,
persistenceCapabilities: 3, persistenceCapabilities: 3,
}, },
@@ -223,7 +226,7 @@ assert.deepEqual(handoffSummary, {
counts: { counts: {
pages: 3, pages: 3,
launcherLinks: 3, launcherLinks: 3,
launchMethods: 39, launchMethods: 42,
controlPageMethods: 11, controlPageMethods: 11,
persistenceCapabilities: 3, persistenceCapabilities: 3,
}, },
@@ -747,6 +750,39 @@ assert.equal(
mountIniPanelShellSessionHandoffWorkflow().phase, mountIniPanelShellSessionHandoffWorkflow().phase,
"blocked", "blocked",
); );
assert.deepEqual(createIniPanelShellWorkflowOverviewContract(), {
pageHref: "./workflow-overview.html",
entryId: "workflow-overview",
apiName: "linuxCncIniPanelWorkflowOverviewApi",
methodNames: [
"getWorkflowOverviewRenderState",
"getWorkflowOverviewDisplayViewModel",
"getWorkflowOverviewSummary",
"getWorkflowOverviewDomContract",
"getWorkflowOverviewDomReadiness",
"renderWorkflowOverview",
"mountWorkflowOverview",
"createWorkflowOverviewRenderState",
],
});
assert.equal(createIniPanelShellWorkflowOverviewReadiness().ready, false);
assert.deepEqual(
createIniPanelShellWorkflowOverviewReadiness({
overviewApi: {
getWorkflowOverviewRenderState: () => handoffPackageRenderState,
},
}).missing,
[
"getWorkflowOverviewDisplayViewModel",
"getWorkflowOverviewSummary",
"getWorkflowOverviewDomContract",
"getWorkflowOverviewDomReadiness",
"renderWorkflowOverview",
"mountWorkflowOverview",
"createWorkflowOverviewRenderState",
],
);
assert.equal(mountIniPanelShellWorkflowOverviewEmbedding().phase, "blocked");
assert.deepEqual( assert.deepEqual(
createIniPanelShellSessionHandoffPackageMountState({ createIniPanelShellSessionHandoffPackageMountState({
handoffPackage, handoffPackage,
@@ -1158,7 +1194,7 @@ assert.deepEqual(createIniPanelShellIntegrationReadiness(blockedManifest), {
counts: { counts: {
pages: 3, pages: 3,
launcherLinks: 3, launcherLinks: 3,
launchMethods: 39, launchMethods: 42,
controlPageMethods: 11, controlPageMethods: 11,
persistenceCapabilities: 3, persistenceCapabilities: 3,
}, },

View File

@@ -33,6 +33,9 @@ import {
createIniPanelShellSessionHandoffWorkflowRenderState, createIniPanelShellSessionHandoffWorkflowRenderState,
createIniPanelShellSessionHandoffWorkflowDomContract, createIniPanelShellSessionHandoffWorkflowDomContract,
createIniPanelShellSessionHandoffWorkflowDomReadiness, createIniPanelShellSessionHandoffWorkflowDomReadiness,
createIniPanelShellWorkflowOverviewContract,
createIniPanelShellWorkflowOverviewReadiness,
mountIniPanelShellWorkflowOverviewEmbedding,
renderIniPanelShellSessionHandoffWorkflowState, renderIniPanelShellSessionHandoffWorkflowState,
mountIniPanelShellSessionHandoffWorkflow, mountIniPanelShellSessionHandoffWorkflow,
renderIniPanelShellSessionHandoffMountState, renderIniPanelShellSessionHandoffMountState,
@@ -116,6 +119,9 @@ assert.equal(typeof createIniPanelShellSessionHandoffWorkflowDisplayViewModel, "
assert.equal(typeof createIniPanelShellSessionHandoffWorkflowRenderState, "function"); assert.equal(typeof createIniPanelShellSessionHandoffWorkflowRenderState, "function");
assert.equal(typeof createIniPanelShellSessionHandoffWorkflowDomContract, "function"); assert.equal(typeof createIniPanelShellSessionHandoffWorkflowDomContract, "function");
assert.equal(typeof createIniPanelShellSessionHandoffWorkflowDomReadiness, "function"); assert.equal(typeof createIniPanelShellSessionHandoffWorkflowDomReadiness, "function");
assert.equal(typeof createIniPanelShellWorkflowOverviewContract, "function");
assert.equal(typeof createIniPanelShellWorkflowOverviewReadiness, "function");
assert.equal(typeof mountIniPanelShellWorkflowOverviewEmbedding, "function");
assert.equal(typeof renderIniPanelShellSessionHandoffWorkflowState, "function"); assert.equal(typeof renderIniPanelShellSessionHandoffWorkflowState, "function");
assert.equal(typeof mountIniPanelShellSessionHandoffWorkflow, "function"); assert.equal(typeof mountIniPanelShellSessionHandoffWorkflow, "function");
assert.equal(typeof renderIniPanelShellSessionHandoffMountState, "function"); assert.equal(typeof renderIniPanelShellSessionHandoffMountState, "function");
@@ -621,6 +627,21 @@ assert.deepEqual(shellViewModel.shellSessionHandoffMountDomContract, {
}, },
renderResultFields: ["rendered", "statusLine", "rowCount", "rowIds", "dataset"], renderResultFields: ["rendered", "statusLine", "rowCount", "rowIds", "dataset"],
}); });
assert.deepEqual(shellViewModel.shellWorkflowOverviewContract, {
pageHref: "./workflow-overview.html",
entryId: "workflow-overview",
apiName: "linuxCncIniPanelWorkflowOverviewApi",
methodNames: [
"getWorkflowOverviewRenderState",
"getWorkflowOverviewDisplayViewModel",
"getWorkflowOverviewSummary",
"getWorkflowOverviewDomContract",
"getWorkflowOverviewDomReadiness",
"renderWorkflowOverview",
"mountWorkflowOverview",
"createWorkflowOverviewRenderState",
],
});
assert.equal( assert.equal(
createIniPanelShellSessionHandoffPackageMountState({ createIniPanelShellSessionHandoffPackageMountState({
handoffPackage: shellViewModel.shellSessionHandoffPackage, handoffPackage: shellViewModel.shellSessionHandoffPackage,
@@ -766,6 +787,9 @@ assert.equal(
shellViewModel.mountShellSessionHandoffWorkflow, shellViewModel.mountShellSessionHandoffWorkflow,
mountIniPanelShellSessionHandoffWorkflow, mountIniPanelShellSessionHandoffWorkflow,
); );
assert.equal(shellViewModel.createWorkflowOverviewContract, createIniPanelShellWorkflowOverviewContract);
assert.equal(shellViewModel.createWorkflowOverviewReadiness, createIniPanelShellWorkflowOverviewReadiness);
assert.equal(shellViewModel.mountWorkflowOverviewEmbedding, mountIniPanelShellWorkflowOverviewEmbedding);
assert.equal(shellViewModel.createShellSessionHandoffPackageReport, createIniPanelShellSessionHandoffPackageReport); assert.equal(shellViewModel.createShellSessionHandoffPackageReport, createIniPanelShellSessionHandoffPackageReport);
assert.equal(shellViewModel.createShellSessionHandoffPackageSchema, createIniPanelShellSessionHandoffPackageSchema); assert.equal(shellViewModel.createShellSessionHandoffPackageSchema, createIniPanelShellSessionHandoffPackageSchema);
assert.equal(shellViewModel.isSupportedShellSessionHandoffPackage, isSupportedIniPanelShellSessionHandoffPackage); assert.equal(shellViewModel.isSupportedShellSessionHandoffPackage, isSupportedIniPanelShellSessionHandoffPackage);
@@ -1105,6 +1129,68 @@ assert.equal(workflowDomMountResult.phase, "blocked");
assert.equal(workflowDomMountResult.ready, false); assert.equal(workflowDomMountResult.ready, false);
assert.equal(workflowDomMountResult.renderResult.rowCount, 5); assert.equal(workflowDomMountResult.renderResult.rowCount, 5);
assert.equal(workflowDomMountResult.renderResult.dataset.handoffScope, "workflow"); assert.equal(workflowDomMountResult.renderResult.dataset.handoffScope, "workflow");
assert.deepEqual(createIniPanelShellWorkflowOverviewContract(), shellViewModel.shellWorkflowOverviewContract);
assert.deepEqual(createIniPanelShellWorkflowOverviewReadiness(), {
apiName: "ini-panel-shell-workflow-overview-readiness",
readinessVersion: 1,
phase: "blocked",
ready: false,
missing: shellViewModel.shellWorkflowOverviewContract.methodNames,
contract: shellViewModel.shellWorkflowOverviewContract,
});
const workflowOverviewApi = {
getWorkflowOverviewRenderState: () => workflowRenderState,
getWorkflowOverviewDisplayViewModel: () => workflowDisplayViewModel,
getWorkflowOverviewSummary: () => workflowSummaryResult,
getWorkflowOverviewDomContract: createIniPanelShellSessionHandoffWorkflowDomContract,
getWorkflowOverviewDomReadiness: createIniPanelShellSessionHandoffWorkflowDomReadiness,
renderWorkflowOverview: renderIniPanelShellSessionHandoffWorkflowState,
mountWorkflowOverview: (options) => mountIniPanelShellSessionHandoffWorkflow({
renderState: workflowRenderState,
...options,
}),
createWorkflowOverviewRenderState: createIniPanelShellSessionHandoffWorkflowRenderState,
};
assert.deepEqual(createIniPanelShellWorkflowOverviewReadiness({
overviewApi: workflowOverviewApi,
}), {
apiName: "ini-panel-shell-workflow-overview-readiness",
readinessVersion: 1,
phase: "ready",
ready: true,
missing: [],
contract: shellViewModel.shellWorkflowOverviewContract,
});
const workflowOverviewEmbedding = mountIniPanelShellWorkflowOverviewEmbedding({
overviewApi: workflowOverviewApi,
mountOptions: {
documentRef: mountDocument,
mountNode,
statusNode: mountStatusNode,
rowsNode: mountRowsNode,
},
});
assert.equal(workflowOverviewEmbedding.apiName, "ini-panel-shell-workflow-overview-embedding");
assert.equal(workflowOverviewEmbedding.embeddingVersion, 1);
assert.equal(workflowOverviewEmbedding.ready, false);
assert.equal(workflowOverviewEmbedding.renderState.statusLine, "Blocked: mount:blocked");
assert.equal(workflowOverviewEmbedding.mountResult.renderResult.rowCount, 5);
assert.equal(workflowOverviewEmbedding.error, null);
assert.deepEqual(
mountIniPanelShellWorkflowOverviewEmbedding({ overviewApi: null }).readiness.missing,
shellViewModel.shellWorkflowOverviewContract.methodNames,
);
assert.equal(
mountIniPanelShellWorkflowOverviewEmbedding({
overviewApi: {
...workflowOverviewApi,
mountWorkflowOverview: () => {
throw new Error("mount failed");
},
},
}).error,
"mount failed",
);
assert.equal( assert.equal(
mountIniPanelShellSessionHandoffWorkflow({ documentRef: mountDocument }).renderResult, mountIniPanelShellSessionHandoffWorkflow({ documentRef: mountDocument }).renderResult,
null, null,