diff --git a/text12.txt b/text12.txt index 6a18537..d9293ce 100644 --- a/text12.txt +++ b/text12.txt @@ -1926,3 +1926,85 @@ host_wasm_opfs_browser_smokes=ok helper(例如 `createIniPanelShellSessionHandoffSummary()`),把 readiness、target page、readonly status fields 与 persistence capabilities 压成外部 shell 可直接显示的一页摘要;仍保持只读,不新增 控制按钮,不解析 G-code。 + +二十四、2026-06-15 继续执行记录:INI panel shell session handoff summary helper + +本轮按“第一原则:加快实质性推进”继续推进 OPFS/session persistence 相关外部 shell 接入能力, +新增只读 shell session handoff summary helper,把 readiness、target page、readonly status fields +与 persistence capabilities 压成外部 shell 可直接显示的一页摘要。 + +完成内容: + +- `wasm-port/runtime/ui/ini-panel/ui-shell.js` 新增 + `createIniPanelShellSessionHandoffSummary()`; +- summary 从 integration manifest、readiness 与 workflow plan 派生: + - `phase: "ready" | "blocked"`; + - `ready`; + - `targetPage`; + - `readiness.phase`; + - `readiness.missing`; + - `readiness.counts`; + - `readiness.apiNames`; + - `persistenceCapabilities`; + - `readonlyStatusFields`; + - `handoffMethods.loadSessionState`; + - `handoffMethods.readStatus`; + - `blockingReasons`; +- `createIniPanelShellViewModel()` 新增: + - `shellSessionHandoffSummary`; + - `createShellSessionHandoffSummary`; +- `verify_ini_panel_shell_integration_manifest.mjs` 覆盖: + - ready handoff summary 完整 shape; + - blocked workflow plan 的 blocking reasons; + - persistence capabilities 与 readonly status fields 返回值不污染后续 summary; +- `verify_ini_panel_ui_shell.mjs` 覆盖 shell view-model 的 handoff summary; +- `verify_ini_panel_entry_docs.mjs` 与 `docs/panel-entry.md` 记录并校验 handoff summary helper; +- 未新增控制按钮; +- 未解析 G-code; +- 未解释 canonical events; +- 未访问 OPFS 或启动 browser/frame; +- 未改变 OPFS/session persistence、LinuxCNC interpreter、tool、parameter、planner 或 + LinuxCNC-owned runtime semantics。 + +验证已通过: + +```bash +git diff --check +wasm-port/tests/ui/node/verify_ini_panel_shell_integration_manifest.sh +wasm-port/tests/ui/node/verify_ini_panel_ui_shell.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_shell_integration_manifest_node_smoke=ok +ini_panel_ui_shell_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_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。下一批建议把 `shellSessionHandoffSummary` 暴露到 launch browser API +或 shell integration browser workflow smoke 中,让真实 browser 外部 shell 可以直接读取 summary +并与 readonly status handoff 结果交叉校验;仍保持只读,不新增控制按钮,不解析 G-code。 diff --git a/wasm-port/docs/panel-entry.md b/wasm-port/docs/panel-entry.md index 7b1b4d2..de2e14c 100644 --- a/wasm-port/docs/panel-entry.md +++ b/wasm-port/docs/panel-entry.md @@ -21,6 +21,7 @@ import { createIniPanelShellIntegrationReadiness, createIniPanelShellIntegrationSchema, createIniPanelShellIntegrationWorkflowPlan, + createIniPanelShellSessionHandoffSummary, createIniPanelShellViewModel, getIniPanelEntryByHref, getVisibleIniPanelEntries, @@ -129,6 +130,9 @@ Use `createIniPanelShellIntegrationWorkflowPlan()` when the outer shell needs a Node-testable read-only plan for the control-page target, required launch and control-page API methods, readiness gate, and readonly status handoff fields before opening a browser frame. +Use `createIniPanelShellSessionHandoffSummary()` when the outer shell needs one +read-only display summary of readiness, target page, persistence capabilities, +readonly status fields, handoff methods, and blocking reasons. 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, diff --git a/wasm-port/runtime/ui/ini-panel/ui-shell.js b/wasm-port/runtime/ui/ini-panel/ui-shell.js index 3c2d076..2a8e818 100644 --- a/wasm-port/runtime/ui/ini-panel/ui-shell.js +++ b/wasm-port/runtime/ui/ini-panel/ui-shell.js @@ -380,9 +380,44 @@ export function createIniPanelShellIntegrationWorkflowPlan({ }; } +export function createIniPanelShellSessionHandoffSummary({ + manifest = createIniPanelShellIntegrationManifest(), + readiness = createIniPanelShellIntegrationReadiness(manifest), + workflowPlan = createIniPanelShellIntegrationWorkflowPlan({ manifest, readiness }), +} = {}) { + const persistenceCapabilities = Array.isArray(manifest?.persistenceCapabilities) + ? manifest.persistenceCapabilities.map(({ id, label, scope }) => ({ id, label, scope })) + : []; + const readonlyStatusFields = Array.isArray(workflowPlan?.readonlyStatusHandoff?.expectedStatusFields) + ? [...workflowPlan.readonlyStatusHandoff.expectedStatusFields] + : []; + return { + phase: workflowPlan.ready ? "ready" : "blocked", + ready: Boolean(workflowPlan.ready), + targetPage: workflowPlan.targetPage, + readiness: { + phase: readiness.phase, + missing: [...readiness.missing], + counts: { ...readiness.counts }, + apiNames: { ...readiness.apiNames }, + }, + persistenceCapabilities, + readonlyStatusFields, + handoffMethods: { + loadSessionState: workflowPlan.readonlyStatusHandoff.loadSessionStateMethod, + readStatus: workflowPlan.readonlyStatusHandoff.readStatusMethod, + }, + blockingReasons: [...workflowPlan.missing], + }; +} + export function createIniPanelShellViewModel(entries = getVisibleIniPanelEntries()) { const shellIntegrationManifest = createIniPanelShellIntegrationManifest(entries); const shellIntegrationReadiness = createIniPanelShellIntegrationReadiness(shellIntegrationManifest); + const shellIntegrationWorkflowPlan = createIniPanelShellIntegrationWorkflowPlan({ + manifest: shellIntegrationManifest, + readiness: shellIntegrationReadiness, + }); return { pages: entries.map(({ id, href, title }) => ({ id, @@ -393,15 +428,18 @@ export function createIniPanelShellViewModel(entries = getVisibleIniPanelEntries shellIntegrationSchema: createIniPanelShellIntegrationSchema(), shellIntegrationManifest, shellIntegrationReadiness, - shellIntegrationWorkflowPlan: createIniPanelShellIntegrationWorkflowPlan({ + shellIntegrationWorkflowPlan, + shellSessionHandoffSummary: createIniPanelShellSessionHandoffSummary({ manifest: shellIntegrationManifest, readiness: shellIntegrationReadiness, + workflowPlan: shellIntegrationWorkflowPlan, }), launchApiSchema: createIniPanelLaunchApiSchema(), launchApiManifest: createIniPanelLaunchApiManifest(entries), isSupportedShellIntegrationManifest: isSupportedIniPanelShellIntegrationManifest, createShellIntegrationReadiness: createIniPanelShellIntegrationReadiness, createShellIntegrationWorkflowPlan: createIniPanelShellIntegrationWorkflowPlan, + createShellSessionHandoffSummary: createIniPanelShellSessionHandoffSummary, controlPage: { browserApiSchema: createControlPageBrowserApiSchema(), browserApiManifest: createControlPageBrowserApiManifest(), diff --git a/wasm-port/tests/ui/node/verify_ini_panel_entry_docs.mjs b/wasm-port/tests/ui/node/verify_ini_panel_entry_docs.mjs index 60835b8..5e621e4 100644 --- a/wasm-port/tests/ui/node/verify_ini_panel_entry_docs.mjs +++ b/wasm-port/tests/ui/node/verify_ini_panel_entry_docs.mjs @@ -18,6 +18,7 @@ import { createIniPanelShellIntegrationReadiness, createIniPanelShellIntegrationSchema, createIniPanelShellIntegrationWorkflowPlan, + createIniPanelShellSessionHandoffSummary, createIniPanelShellViewModel, createMachineSessionReadonlyStatus, createMachineSessionReadonlyStatusApiManifest, @@ -65,6 +66,7 @@ const requiredShellExports = [ "createIniPanelShellIntegrationReadiness", "createIniPanelShellIntegrationSchema", "createIniPanelShellIntegrationWorkflowPlan", + "createIniPanelShellSessionHandoffSummary", "createIniPanelShellViewModel", "getIniPanelEntryByHref", "getVisibleIniPanelEntries", @@ -142,6 +144,14 @@ assert.deepEqual( readiness: createIniPanelShellViewModel().shellIntegrationReadiness, }), ); +assert.deepEqual( + createIniPanelShellViewModel().shellSessionHandoffSummary, + createIniPanelShellSessionHandoffSummary({ + manifest: createIniPanelShellViewModel().shellIntegrationManifest, + readiness: createIniPanelShellViewModel().shellIntegrationReadiness, + workflowPlan: createIniPanelShellViewModel().shellIntegrationWorkflowPlan, + }), +); assert.equal(isSupportedIniPanelShellIntegrationManifest(createIniPanelShellViewModel().shellIntegrationManifest), true); assert.deepEqual(createIniPanelShellViewModel().launchApiSchema, createIniPanelLaunchApiSchema()); assert.deepEqual(createIniPanelShellViewModel().launchApiManifest, createIniPanelLaunchApiManifest()); @@ -196,6 +206,7 @@ assert.match(shellText, /\bexport function createIniPanelShellIntegrationSchema\ assert.match(shellText, /\bexport function createIniPanelShellIntegrationManifest\b/); assert.match(shellText, /\bexport function createIniPanelShellIntegrationReadiness\b/); assert.match(shellText, /\bexport function createIniPanelShellIntegrationWorkflowPlan\b/); +assert.match(shellText, /\bexport function createIniPanelShellSessionHandoffSummary\b/); assert.match(shellText, /\bexport function isSupportedIniPanelControlPageApiManifest\b/); assert.match(shellText, /\bexport function isSupportedIniPanelLaunchApiManifest\b/); assert.match(shellText, /\bexport function isSupportedIniPanelShellIntegrationManifest\b/); @@ -228,6 +239,7 @@ assert.match(docText, /\bcreateIniPanelShellIntegrationManifest\b/); assert.match(docText, /\bcreateIniPanelShellIntegrationReadiness\b/); assert.match(docText, /\bcreateIniPanelShellIntegrationSchema\b/); assert.match(docText, /\bcreateIniPanelShellIntegrationWorkflowPlan\b/); +assert.match(docText, /\bcreateIniPanelShellSessionHandoffSummary\b/); assert.match(docText, /\bisSupportedIniPanelControlPageApiManifest\b/); assert.match(docText, /\bisSupportedIniPanelLaunchApiManifest\b/); assert.match(docText, /\bisSupportedIniPanelShellIntegrationManifest\b/); diff --git a/wasm-port/tests/ui/node/verify_ini_panel_shell_integration_manifest.mjs b/wasm-port/tests/ui/node/verify_ini_panel_shell_integration_manifest.mjs index 95cab0e..805dc66 100644 --- a/wasm-port/tests/ui/node/verify_ini_panel_shell_integration_manifest.mjs +++ b/wasm-port/tests/ui/node/verify_ini_panel_shell_integration_manifest.mjs @@ -14,6 +14,7 @@ import { createIniPanelShellIntegrationReadiness, createIniPanelShellIntegrationSchema, createIniPanelShellIntegrationWorkflowPlan, + createIniPanelShellSessionHandoffSummary, getVisibleIniPanelEntries, isSupportedIniPanelShellIntegrationManifest, } from "../../../runtime/ui/ini-panel/ui-shell.js"; @@ -28,6 +29,7 @@ const schema = createIniPanelShellIntegrationSchema(); const manifest = createIniPanelShellIntegrationManifest(); const readiness = createIniPanelShellIntegrationReadiness(manifest); const workflowPlan = createIniPanelShellIntegrationWorkflowPlan({ manifest, readiness }); +const handoffSummary = createIniPanelShellSessionHandoffSummary({ manifest, readiness, workflowPlan }); assert.deepEqual(schema, { apiName: "ini-panel-shell-integration", @@ -130,6 +132,63 @@ assert.deepEqual(workflowPlan, { ], }, }); +assert.deepEqual(handoffSummary, { + phase: "ready", + ready: true, + targetPage: { + entryId: "control-view", + href: "./control-page.html", + }, + readiness: { + phase: "ready", + missing: [], + counts: { + pages: 2, + launcherLinks: 2, + launchMethods: 8, + controlPageMethods: 11, + persistenceCapabilities: 3, + }, + apiNames: { + integration: "ini-panel-shell-integration", + launch: "ini-panel-launch", + controlPage: "ini-panel-control-page", + }, + }, + persistenceCapabilities: [ + { + id: "machine-files", + label: "Machine text files", + scope: "opfs", + }, + { + id: "session-snapshot", + label: "Machine session snapshot", + scope: "opfs", + }, + { + id: "readonly-status", + label: "Read-only machine session status", + scope: "control-page", + }, + ], + readonlyStatusFields: [ + "stateBundle", + "workflowStatus", + "overview", + "controlView", + "report", + "runReadiness", + "run", + "session", + "status", + ], + handoffMethods: { + loadSessionState: "loadControlPageSessionState", + readStatus: "readControlPageStatus", + }, + blockingReasons: [], +}); const blockedManifest = { ...createIniPanelShellIntegrationManifest(), compatibility: { @@ -168,6 +227,18 @@ assert.deepEqual( }).missing, ["launchCompatibility"], ); +const blockedWorkflowPlan = createIniPanelShellIntegrationWorkflowPlan({ + manifest: blockedManifest, + readiness: createIniPanelShellIntegrationReadiness(blockedManifest), +}); +assert.deepEqual( + createIniPanelShellSessionHandoffSummary({ + manifest: blockedManifest, + readiness: createIniPanelShellIntegrationReadiness(blockedManifest), + workflowPlan: blockedWorkflowPlan, + }).blockingReasons, + ["launchCompatibility"], +); const missingTargetPlan = createIniPanelShellIntegrationWorkflowPlan({ manifest, controlEntryId: "missing-entry", @@ -189,6 +260,10 @@ assert.equal(missingControlMethodPlan.ready, false); assert.deepEqual(missingControlMethodPlan.missing, [ "controlPageApi.loadControlPageSessionState", ]); +handoffSummary.persistenceCapabilities[0].label = "changed"; +handoffSummary.readonlyStatusFields.push("changedField"); +assert.equal(createIniPanelShellSessionHandoffSummary().persistenceCapabilities[0].label, "Machine text files"); +assert.equal(createIniPanelShellSessionHandoffSummary().readonlyStatusFields.includes("changedField"), false); const malformedReadiness = createIniPanelShellIntegrationReadiness({ apiName: "other", }); @@ -262,6 +337,7 @@ assert.match(shellText, /\bexport function createIniPanelShellIntegrationSchema\ assert.match(shellText, /\bexport function createIniPanelShellIntegrationManifest\b/); assert.match(shellText, /\bexport function createIniPanelShellIntegrationReadiness\b/); assert.match(shellText, /\bexport function createIniPanelShellIntegrationWorkflowPlan\b/); +assert.match(shellText, /\bexport function createIniPanelShellSessionHandoffSummary\b/); assert.match(shellText, /\bexport function isSupportedIniPanelShellIntegrationManifest\b/); assert.match(launchText, /\bgetShellIntegrationSchema\b/); assert.match(launchText, /\bgetShellIntegrationManifest\b/); @@ -269,6 +345,7 @@ assert.match(launchText, /\bgetShellIntegrationReadiness\b/); assert.match(docText, /\bcreateIniPanelShellIntegrationManifest\b/); assert.match(docText, /\bcreateIniPanelShellIntegrationReadiness\b/); assert.match(docText, /\bcreateIniPanelShellIntegrationWorkflowPlan\b/); +assert.match(docText, /\bcreateIniPanelShellSessionHandoffSummary\b/); assert.match(docText, /\bisSupportedIniPanelShellIntegrationManifest\b/); assert.doesNotMatch(shellText, /\bparseGcode\b|\bcanonicalEvents\b|\btoolTable\b|\bplanner\b/); diff --git a/wasm-port/tests/ui/node/verify_ini_panel_ui_shell.mjs b/wasm-port/tests/ui/node/verify_ini_panel_ui_shell.mjs index da43d2e..d730eac 100644 --- a/wasm-port/tests/ui/node/verify_ini_panel_ui_shell.mjs +++ b/wasm-port/tests/ui/node/verify_ini_panel_ui_shell.mjs @@ -14,6 +14,7 @@ import { createIniPanelShellIntegrationReadiness, createIniPanelShellIntegrationSchema, createIniPanelShellIntegrationWorkflowPlan, + createIniPanelShellSessionHandoffSummary, createIniPanelShellViewModel, createMachineSessionReadonlyStatus, createMachineSessionReadonlyStatusApiManifest, @@ -66,6 +67,7 @@ assert.equal(typeof createIniPanelShellIntegrationManifest, "function"); assert.equal(typeof createIniPanelShellIntegrationReadiness, "function"); assert.equal(typeof createIniPanelShellIntegrationSchema, "function"); assert.equal(typeof createIniPanelShellIntegrationWorkflowPlan, "function"); +assert.equal(typeof createIniPanelShellSessionHandoffSummary, "function"); assert.equal(typeof loadMachineSessionForControlPageWorkflow, "function"); assert.equal(typeof readMachineSessionReadonlyStatus, "function"); assert.equal(typeof refreshMachineSessionControlPage, "function"); @@ -122,6 +124,14 @@ assert.deepEqual( readiness: shellViewModel.shellIntegrationReadiness, }), ); +assert.deepEqual( + shellViewModel.shellSessionHandoffSummary, + createIniPanelShellSessionHandoffSummary({ + manifest: shellViewModel.shellIntegrationManifest, + readiness: shellViewModel.shellIntegrationReadiness, + workflowPlan: shellViewModel.shellIntegrationWorkflowPlan, + }), +); assert.equal( isSupportedIniPanelShellIntegrationManifest(shellViewModel.shellIntegrationManifest), true, @@ -154,6 +164,7 @@ assert.equal(shellViewModel.controlPage.createController, createMachineSessionCo assert.equal(shellViewModel.isSupportedShellIntegrationManifest, isSupportedIniPanelShellIntegrationManifest); assert.equal(shellViewModel.createShellIntegrationReadiness, createIniPanelShellIntegrationReadiness); assert.equal(shellViewModel.createShellIntegrationWorkflowPlan, createIniPanelShellIntegrationWorkflowPlan); +assert.equal(shellViewModel.createShellSessionHandoffSummary, createIniPanelShellSessionHandoffSummary); assert.deepEqual(shellViewModel.controlPage.contract, createIniPanelShellControlPageContract()); assert.equal(shellViewModel.controlPage.isSupportedBrowserApiManifest, isSupportedIniPanelControlPageApiManifest); assert.equal(shellViewModel.controlPage.loadSessionState, loadMachineSessionForControlPageWorkflow);