推进 text12 接续和 control-page session readiness workflow

This commit is contained in:
2026-06-15 04:57:21 +08:00
parent ed5d03ea1b
commit 98d236250f
13 changed files with 621 additions and 0 deletions

307
text12.txt Normal file
View File

@@ -0,0 +1,307 @@
项目整体完成情况与后续执行计划
生成时间2026-06-15 CST
本文件接替 `text11.txt`。后续工作按 `text12.txt` 执行;除非明确要求审计旧记录,
不要再回到 `text1/text2/text3/text4/text5/text6/text7/text8/text9/text10/text11`
扩展进度。`text11.txt` 作为历史归档保留。
一、第一原则:加快实质性推进
后续批次必须以“实质性能力优先”为第一遵守原则。
1. 优先级从高到低:
- OPFS/session persistence
- SDK/API surface
- browser/UI workflow
- sim config coverage promotion
- host/runtime boundary proof
- 文档化可执行工作流。
2. 每批默认应产出至少一种可直接复用的能力:
- 可调用 helper
- 可复用 API
- 可验证用户流程;
- 可执行 gate
- 可提升覆盖面的 runtime boundary proof
- 能直接指导下一步实现的设计/清单。
3. 暂停低收益推进:
- 不再把“局部常量化、路径变量收敛、断言分组整理”作为主目标;
- 只有当它支撑可复用 workflow/API/gate 时,才允许做测试胶水;
- 禁止为了推进而扩大 CNC 语义边界;
- 禁止启动性能测试,除非已有明确用户流程需要性能基线。
4. LinuxCNC 仍是唯一 CNC 语义来源:
- G-code、remap、tool、parameter、planner、kinematics、user-M、tool DB、Python remap
等语义必须来自 LinuxCNC upstream 或 vendored LinuxCNC C/C++/配置/脚本源码;
- JS/browser 只允许做 build、source sync、runtime shim、filesystem staging、OPFS、
WASM/browser boundary、测试胶水、文档和机器可读 gate/artifact
- 不在 JS 中重写 G-code 或 CNC 语义;
- 不修改 vendored LinuxCNC 源文件来让测试通过;
- 不把 full-process/HAL/UI/Python/tool DB 依赖伪装成 standalone pass
- 不把 expected failure 硬改成 pass除非已有 LinuxCNC-owned runtime proof并且
native、Node、browser gate 都按顺序通过;
- browser 不能依赖目录枚举。
二、续接状态与防重规则
1. 当前续接点。
`text11.txt` 已推进到:
```text
二十四、2026-06-15 继续执行记录INI panel control-page refresh workflow helper
```
该批已提交:
```text
ed5d03e 推进 INI 面板 control-page refresh workflow helper
```
本文件从 `text12.txt` 重新开始记录后续进度。
2. 每轮开始前必须先执行:
```bash
git status --short
git log -5 --oneline
tail -n 120 text12.txt
awk '/^[一二三四五六七八九十百]+、/ {print}' text12.txt | sort | uniq -d
```
执行规则:
- 如果 `git status --short` 是 clean且最新提交已经包含当前建议工作不要重复追加记录
- 每轮只追加一个新的中文编号记录;
- 提交前再次检查标题唯一性;
- `text12.txt` 只记录新的后续进度,不复制 `text11.txt` 的长历史;
- 每批完成后,如果要写记录,先写 `text12.txt`,再提交该批代码和记录;
- 如果发现重复记录,先做 dedupe 清理并提交,再继续新功能或新 guard
- `text11.txt` 不再追加新进度,除非明确要求审计或修正历史记录。
三、当前完成情况摘要
1. 当前工作树状态。
切换到 `text12.txt` 前已确认:
```text
git status --short: clean
```
当前最近提交:
```text
ed5d03e 推进 INI 面板 control-page refresh workflow helper
213514f 推进 INI 面板 shell view-model helper
b330f7b 推进 INI 面板 launcher link view-model helper
ac46345 推进 INI 面板 entry docs strict gate
ef4769b 推进 INI 面板 entry doc
```
2. 关键基线保持稳定。
- `linuxcnc/nc_files` native Layer 1
- `total 107`
- `pass 101`
- `expected_fail 6`
- `unexpected_fail 0`
- `linuxcnc/configs/sim` native Layer 2
- `total 159`
- `pass 151`
- `expected_fail 8`
- `unexpected_fail 0`
- Node sim-config inventory
- `executed=28`
- `passed=28`
- `skipped=131`
- `unexpected_fail=0`
3. blocked runtime family 仍未 promotion。
仍 blocked不允许 promotion
- `L4-USER-M-PROCESS`
- `L4-TOOL-DB`
- `L4-PYTHON-REMAP`
当前 host 仍按缺少 LinuxCNC host runtime 处理,不执行任何:
```bash
ENABLE_MILLTURN_USER_M_RUNTIME_PROBE=1 bash wasm-port/tests/native/probe_millturn_user_m_runtime.sh
ENABLE_TOOL_DB_RUNTIME_PROBE=1 bash wasm-port/tests/native/probe_tool_db_runtime.sh
ENABLE_PYTHON_REMAP_RUNTIME_PROBE=1 bash wasm-port/tests/native/probe_python_remap_runtime.sh
```
四、当前实质能力进展
1. INI panel/UI helper 能力。
当前 UI helper 已覆盖:
- run summary
- run workflow
- session snapshot summary
- session load summary
- machine file save/load summary
- panel state summary
- session snapshot save/restore workflow
- entry manifest、launcher link view-model、shell view-model
- read-only control-page controller
- read-only control-page refresh workflow。
当前聚合验证入口:
```bash
wasm-port/tests/ui/node/verify_ui_node_smokes.sh
```
当前 `verify_ui_node_smokes.sh` 覆盖:
```text
ini_panel_run_summary_node_smoke=ok
ini_panel_run_workflow_node_smoke=ok
ini_panel_session_summary_node_smoke=ok
ini_panel_session_workflow_node_smoke=ok
ini_panel_machine_file_summary_node_smoke=ok
ini_panel_state_summary_node_smoke=ok
ini_panel_control_page_controller_node_smoke=ok
ini_panel_control_page_refresh_workflow_node_smoke=ok
ini_panel_entry_manifest_node_smoke=ok
ini_panel_ui_shell_node_smoke=ok
ini_panel_entry_docs_node_smoke=ok
ui_node_smokes=ok
```
2. Browser/UI workflow 验证。
当前 browser INI panel smoke 覆盖真实 OPFS/session persistence workflow
```bash
SKIP_INI_BUILD=1 SKIP_INTERP_BUILD=1 wasm-port/tests/browser/verify_ini_panel_browser.sh
```
关键输出:
```text
browser_ini_opfs_smoke=ok
browser_ini_control_page_smoke=ok
browser_ini_launch_smoke=ok
```
五、当前必须保持通过的验证入口
每批后续改动至少运行:
```bash
git diff --check
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
SKIP_INI_BUILD=1 SKIP_INTERP_BUILD=1 wasm-port/tests/browser/verify_ini_panel_browser.sh
wasm-port/tests/ui/node/verify_ui_node_smokes.sh
wasm-port/tests/host/verify_host_smokes.sh
```
如果涉及 browser interpreter smoke再运行
```bash
SKIP_INI_BUILD=1 SKIP_INTERP_BUILD=1 wasm-port/tests/browser/verify_interp_browser.sh
```
涉及 native baseline 或大范围 source/staging 改动时,再运行:
```bash
wasm-port/tests/native/verify_nc_files.sh
wasm-port/tests/native/verify_sim_configs.sh
```
六、下一步建议
继续推进 OPFS/session persistence 的实质链路。下一批建议抽出
`loadMachineSessionForControlPageWorkflow()` 或等价 helper让 read-only control page 能以明确
状态模型描述 embedded panel API readiness 和首帧 session state仍保持只读不新增控制按钮
不解析 G-code。
一、2026-06-15 继续执行记录INI panel control-page session readiness workflow helper
本轮按“第一原则:加快实质性推进”切换到 `text12.txt` 续接,并推进 OPFS/session
persistence 相关 browser/UI workflow新增 read-only control-page session readiness workflow
让外层页面能以明确状态模型读取 embedded panel API readiness 和首帧 session state。
完成内容:
- 新建 `text12.txt`,接替 `text11.txt`;后续记录从本文件继续;
- 新增 `wasm-port/runtime/ui/ini-panel/control-page-session-workflow.js`
- 新增 `createControlPageSessionLoadState(input)`,固定 control-page session load 状态结构;
- 新增 `loadMachineSessionForControlPageWorkflow(input)`,统一描述:
- `waiting-panel`embedded panel frame 尚不可读;
- `waiting-api`panel window 可读但 API 尚未暴露;
- `waiting-session`API 已可读但 control view 尚未就绪;
- `ready`state bundle、workflow status 和 control view 可读取;
- `error`:读取 panel window/API/control view 时失败;
- `createMachineSessionControlPageController()` 新增 `loadSessionState()`
- `control-page.js` 对浏览器页面暴露 `loadControlPageSessionState`
- `ui-shell.js` re-export session readiness workflow helper并在
`createIniPanelShellViewModel()` 中暴露 `controlPage.loadSessionState`
- 新增 `wasm-port/tests/ui/node/verify_ini_panel_control_page_session_workflow.mjs` 和 `.sh`
- `verify_ui_node_smokes.sh` 纳入 session readiness workflow smoke
- browser control-page smoke 验证 `loadControlPageSessionState().phase === "ready"`
- `panel-entry.md` 和 entry docs gate 同步记录 session readiness workflow API
- 未新增控制按钮;
- 未解析 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_control_page_session_workflow.sh
wasm-port/tests/ui/node/verify_ini_panel_control_page_controller.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_control_page_session_workflow_node_smoke=ok
ini_panel_control_page_controller_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
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` 按钮路径抽成
`loadMachineSessionIntoWasmWorkflow()`,统一返回 loaded session、load summary、field state 和
log lines仍保持由 LinuxCNC/OPFS bridge 执行实际语义,不在 JS 中解释 CNC 行为。

View File

@@ -15,6 +15,8 @@ import {
getIniPanelEntryByHref, getIniPanelEntryByHref,
getVisibleIniPanelEntries, getVisibleIniPanelEntries,
createMachineSessionControlPageController, createMachineSessionControlPageController,
loadMachineSessionForControlPageWorkflow,
createControlPageSessionLoadState,
refreshMachineSessionControlPage, refreshMachineSessionControlPage,
controlPageStatusText, controlPageStatusText,
renderMachineSessionControlView, renderMachineSessionControlView,
@@ -57,6 +59,13 @@ refresh result with `phase`, `statusText`, `view`, `renderResult`, and `error`.
the read-only status line, and `createControlPageRefreshState()` is the stable the read-only status line, and `createControlPageRefreshState()` is the stable
state-shape helper used by the workflow. state-shape helper used by the workflow.
The control-page session workflow lives in
`runtime/ui/ini-panel/control-page-session-workflow.js`. Use
`loadMachineSessionForControlPageWorkflow()` when an outer shell needs a
read-only readiness snapshot for the embedded panel API and first control view.
`createControlPageSessionLoadState()` is the stable state-shape helper for
`waiting-panel`, `waiting-api`, `waiting-session`, `ready`, and `error` phases.
## Boundary ## Boundary
The entry shell only links pages and renders already-observed panel state. It The entry shell only links pages and renders already-observed panel state. It

View File

@@ -1,6 +1,9 @@
import { import {
refreshMachineSessionControlPage, refreshMachineSessionControlPage,
} from "./control-page-refresh-workflow.js"; } from "./control-page-refresh-workflow.js";
import {
loadMachineSessionForControlPageWorkflow,
} from "./control-page-session-workflow.js";
export function createMachineSessionControlPageController({ export function createMachineSessionControlPageController({
panelFrame, panelFrame,
@@ -50,6 +53,14 @@ export function createMachineSessionControlPageController({
}); });
} }
function loadSessionState() {
return loadMachineSessionForControlPageWorkflow({
getPanelWindow,
getPanelApi,
getControlView,
});
}
function startPolling() { function startPolling() {
if (!refreshTimer) { if (!refreshTimer) {
refreshTimer = setIntervalFn(render, pollMs); refreshTimer = setIntervalFn(render, pollMs);
@@ -72,6 +83,7 @@ export function createMachineSessionControlPageController({
getControlView, getControlView,
getPanelApi, getPanelApi,
getPanelWindow, getPanelWindow,
loadSessionState,
mount, mount,
refresh, refresh,
render, render,

View File

@@ -0,0 +1,84 @@
export function createControlPageSessionLoadState({
phase,
panelReady = false,
apiReady = false,
controlViewReady = false,
stateBundle = null,
workflowStatus = null,
controlView = null,
error = null,
} = {}) {
return {
phase,
panelReady,
apiReady,
controlViewReady,
stateBundle,
workflowStatus,
controlView,
error,
};
}
export function loadMachineSessionForControlPageWorkflow({
getPanelWindow,
getPanelApi,
getControlView,
} = {}) {
if (typeof getPanelWindow !== "function") {
throw new Error("A panel window getter is required.");
}
if (typeof getPanelApi !== "function") {
throw new Error("A panel API getter is required.");
}
if (typeof getControlView !== "function") {
throw new Error("A control view getter is required.");
}
let panelWindow = null;
let panelApi = null;
let controlView = null;
try {
panelWindow = getPanelWindow();
panelApi = getPanelApi();
controlView = getControlView();
} catch (error) {
return createControlPageSessionLoadState({
phase: "error",
error: error.message,
});
}
if (!panelWindow) {
return createControlPageSessionLoadState({
phase: "waiting-panel",
});
}
if (!panelApi) {
return createControlPageSessionLoadState({
phase: "waiting-api",
panelReady: true,
});
}
if (!controlView) {
return createControlPageSessionLoadState({
phase: "waiting-session",
panelReady: true,
apiReady: true,
stateBundle: panelApi.getMachineSessionStateBundle?.() ?? null,
workflowStatus: panelApi.getMachineSessionWorkflowStatus?.() ?? null,
});
}
return createControlPageSessionLoadState({
phase: "ready",
panelReady: true,
apiReady: true,
controlViewReady: true,
stateBundle: panelApi.getMachineSessionStateBundle?.() ?? null,
workflowStatus: panelApi.getMachineSessionWorkflowStatus?.() ?? null,
controlView,
});
}

View File

@@ -21,6 +21,7 @@ controller.mount();
window.linuxCncIniPanelControlPageApi = { window.linuxCncIniPanelControlPageApi = {
getControlView: controller.getControlView, getControlView: controller.getControlView,
getPanelApi: controller.getPanelApi, getPanelApi: controller.getPanelApi,
loadControlPageSessionState: controller.loadSessionState,
refreshControlPage: controller.refresh, refreshControlPage: controller.refresh,
renderControlPage: controller.render, renderControlPage: controller.render,
}; };

View File

@@ -14,11 +14,19 @@ import {
createControlPageRefreshState, createControlPageRefreshState,
refreshMachineSessionControlPage, refreshMachineSessionControlPage,
} from "./control-page-refresh-workflow.js"; } from "./control-page-refresh-workflow.js";
import {
createControlPageSessionLoadState,
loadMachineSessionForControlPageWorkflow,
} from "./control-page-session-workflow.js";
export { export {
controlPageStatusText, controlPageStatusText,
createControlPageRefreshState, createControlPageRefreshState,
refreshMachineSessionControlPage, refreshMachineSessionControlPage,
} from "./control-page-refresh-workflow.js"; } from "./control-page-refresh-workflow.js";
export {
createControlPageSessionLoadState,
loadMachineSessionForControlPageWorkflow,
} from "./control-page-session-workflow.js";
export { export {
INI_PANEL_ENTRIES, INI_PANEL_ENTRIES,
getIniPanelEntryByHref, getIniPanelEntryByHref,
@@ -53,6 +61,7 @@ export function createIniPanelShellViewModel(entries = getVisibleIniPanelEntries
launcherLinks: createIniPanelLauncherLinkViewModel(entries), launcherLinks: createIniPanelLauncherLinkViewModel(entries),
controlPage: { controlPage: {
createController: createMachineSessionControlPageController, createController: createMachineSessionControlPageController,
loadSessionState: loadMachineSessionForControlPageWorkflow,
refresh: refreshMachineSessionControlPage, refresh: refreshMachineSessionControlPage,
renderView: renderMachineSessionControlView, renderView: renderMachineSessionControlView,
}, },

View File

@@ -64,6 +64,7 @@ TOOL_TABLE = browser-tool.tbl
if ( if (
!controlPageApi?.getControlView || !controlPageApi?.getControlView ||
!controlPageApi?.getPanelApi || !controlPageApi?.getPanelApi ||
!controlPageApi?.loadControlPageSessionState ||
!controlPageApi?.refreshControlPage || !controlPageApi?.refreshControlPage ||
!controlPageApi?.renderControlPage !controlPageApi?.renderControlPage
) { ) {
@@ -88,6 +89,11 @@ TOOL_TABLE = browser-tool.tbl
"rendered", "rendered",
"control page refresh phase", "control page refresh phase",
); );
assertEqual(
controlPageApi.loadControlPageSessionState().phase,
"ready",
"control page session load phase",
);
panelDoc.getElementById("ini-editor").value = iniText; panelDoc.getElementById("ini-editor").value = iniText;
panelDoc.getElementById("query").click(); panelDoc.getElementById("query").click();

View File

@@ -39,6 +39,17 @@ const expectedView = {
}, },
], ],
}; };
const expectedStateBundle = {
report: {
readiness: {
ini: "INI WASM: ready",
},
},
};
const expectedWorkflowStatus = {
lastAction: "run-gcode",
error: null,
};
let intervalCallback = null; let intervalCallback = null;
const panelFrame = createPanelFrame(); const panelFrame = createPanelFrame();
@@ -69,11 +80,23 @@ assert.equal(typeof intervalCallback, "function");
const panelApi = { const panelApi = {
getMachineSessionControlView: () => expectedView, getMachineSessionControlView: () => expectedView,
getMachineSessionStateBundle: () => expectedStateBundle,
getMachineSessionWorkflowStatus: () => expectedWorkflowStatus,
}; };
panelFrame.contentWindow = { linuxCncIniPanelApi: panelApi }; panelFrame.contentWindow = { linuxCncIniPanelApi: panelApi };
assert.equal(controller.getPanelApi(), panelApi); assert.equal(controller.getPanelApi(), panelApi);
assert.equal(controller.getControlView(), expectedView); assert.equal(controller.getControlView(), expectedView);
assert.deepEqual(controller.loadSessionState(), {
phase: "ready",
panelReady: true,
apiReady: true,
controlViewReady: true,
stateBundle: expectedStateBundle,
workflowStatus: expectedWorkflowStatus,
controlView: expectedView,
error: null,
});
const renderResult = controller.render(); const renderResult = controller.render();
assert.deepEqual(renderResult, { statusNode: { textContent: "rendered" } }); assert.deepEqual(renderResult, { statusNode: { textContent: "rendered" } });
assert.equal(statusNode.textContent, "run-gcode | ok | -"); assert.equal(statusNode.textContent, "run-gcode | ok | -");

View File

@@ -0,0 +1,121 @@
import assert from "node:assert/strict";
import {
createControlPageSessionLoadState,
loadMachineSessionForControlPageWorkflow,
} from "../../../runtime/ui/ini-panel/control-page-session-workflow.js";
const stateBundle = {
report: {
readiness: {
ini: "INI WASM: ready",
interp: "Interpreter WASM: ready",
opfs: "OPFS: ready",
},
},
};
const workflowStatus = {
lastAction: "load-session",
error: null,
};
const controlView = {
status: {
lastAction: "load-session",
runStatus: null,
error: null,
},
sections: [],
};
assert.deepEqual(createControlPageSessionLoadState({ phase: "waiting-panel" }), {
phase: "waiting-panel",
panelReady: false,
apiReady: false,
controlViewReady: false,
stateBundle: null,
workflowStatus: null,
controlView: null,
error: null,
});
assert.deepEqual(
loadMachineSessionForControlPageWorkflow({
getPanelWindow: () => null,
getPanelApi: () => null,
getControlView: () => null,
}),
createControlPageSessionLoadState({ phase: "waiting-panel" }),
);
assert.deepEqual(
loadMachineSessionForControlPageWorkflow({
getPanelWindow: () => ({}),
getPanelApi: () => null,
getControlView: () => null,
}),
createControlPageSessionLoadState({
phase: "waiting-api",
panelReady: true,
}),
);
const panelApiWithoutControlView = {
getMachineSessionStateBundle: () => stateBundle,
getMachineSessionWorkflowStatus: () => workflowStatus,
};
assert.deepEqual(
loadMachineSessionForControlPageWorkflow({
getPanelWindow: () => ({}),
getPanelApi: () => panelApiWithoutControlView,
getControlView: () => null,
}),
createControlPageSessionLoadState({
phase: "waiting-session",
panelReady: true,
apiReady: true,
stateBundle,
workflowStatus,
}),
);
const panelApi = {
...panelApiWithoutControlView,
getMachineSessionControlView: () => controlView,
};
assert.deepEqual(
loadMachineSessionForControlPageWorkflow({
getPanelWindow: () => ({}),
getPanelApi: () => panelApi,
getControlView: () => controlView,
}),
createControlPageSessionLoadState({
phase: "ready",
panelReady: true,
apiReady: true,
controlViewReady: true,
stateBundle,
workflowStatus,
controlView,
}),
);
assert.deepEqual(
loadMachineSessionForControlPageWorkflow({
getPanelWindow: () => {
throw new Error("cross-origin frame");
},
getPanelApi: () => null,
getControlView: () => null,
}),
createControlPageSessionLoadState({
phase: "error",
error: "cross-origin frame",
}),
);
assert.throws(
() => loadMachineSessionForControlPageWorkflow(),
/panel window getter/,
);
console.log("ini_panel_control_page_session_workflow_node_smoke=ok");

View File

@@ -0,0 +1,6 @@
#!/usr/bin/env bash
set -euo pipefail
ROOT_DIR="$(cd "$(dirname "$0")/../../.." && pwd)"
node "$ROOT_DIR/tests/ui/node/verify_ini_panel_control_page_session_workflow.mjs"

View File

@@ -6,8 +6,10 @@ import { dirname, resolve } from "node:path";
import { import {
INI_PANEL_ENTRIES, INI_PANEL_ENTRIES,
controlPageStatusText, controlPageStatusText,
createControlPageSessionLoadState,
createIniPanelLauncherLinkViewModel, createIniPanelLauncherLinkViewModel,
createIniPanelShellViewModel, createIniPanelShellViewModel,
loadMachineSessionForControlPageWorkflow,
refreshMachineSessionControlPage, refreshMachineSessionControlPage,
getVisibleIniPanelEntries, getVisibleIniPanelEntries,
} from "../../../runtime/ui/ini-panel/ui-shell.js"; } from "../../../runtime/ui/ini-panel/ui-shell.js";
@@ -20,15 +22,21 @@ 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",
); );
const sessionWorkflowText = readFileSync(
resolve(root, "runtime/ui/ini-panel/control-page-session-workflow.js"),
"utf8",
);
const requiredShellExports = [ const requiredShellExports = [
"INI_PANEL_ENTRIES", "INI_PANEL_ENTRIES",
"controlPageStatusText", "controlPageStatusText",
"createControlPageSessionLoadState",
"createIniPanelLauncherLinkViewModel", "createIniPanelLauncherLinkViewModel",
"createIniPanelShellViewModel", "createIniPanelShellViewModel",
"getIniPanelEntryByHref", "getIniPanelEntryByHref",
"getVisibleIniPanelEntries", "getVisibleIniPanelEntries",
"createMachineSessionControlPageController", "createMachineSessionControlPageController",
"loadMachineSessionForControlPageWorkflow",
"refreshMachineSessionControlPage", "refreshMachineSessionControlPage",
"renderMachineSessionControlView", "renderMachineSessionControlView",
]; ];
@@ -47,6 +55,14 @@ for (const exportName of [
assert.match(docText, new RegExp(`\\b${exportName}\\b`)); assert.match(docText, new RegExp(`\\b${exportName}\\b`));
} }
for (const exportName of [
"createControlPageSessionLoadState",
"loadMachineSessionForControlPageWorkflow",
]) {
assert.match(sessionWorkflowText, new RegExp(`\\bexport function ${exportName}\\b`));
assert.match(docText, new RegExp(`\\b${exportName}\\b`));
}
const requiredPages = [ const requiredPages = [
["Launch", "runtime/ui/ini-panel/launch.html"], ["Launch", "runtime/ui/ini-panel/launch.html"],
["Edit and run", "runtime/ui/ini-panel/index.html"], ["Edit and run", "runtime/ui/ini-panel/index.html"],
@@ -69,7 +85,18 @@ assert.deepEqual(
assert.deepEqual(createIniPanelShellViewModel().pages, getVisibleIniPanelEntries()); assert.deepEqual(createIniPanelShellViewModel().pages, getVisibleIniPanelEntries());
assert.deepEqual(createIniPanelShellViewModel().launcherLinks, createIniPanelLauncherLinkViewModel()); assert.deepEqual(createIniPanelShellViewModel().launcherLinks, createIniPanelLauncherLinkViewModel());
assert.equal(controlPageStatusText({ lastAction: "run-gcode", runStatus: "ok", error: null }), "run-gcode | ok | -"); assert.equal(controlPageStatusText({ lastAction: "run-gcode", runStatus: "ok", error: null }), "run-gcode | ok | -");
assert.deepEqual(createControlPageSessionLoadState({ phase: "waiting-panel" }), {
phase: "waiting-panel",
panelReady: false,
apiReady: false,
controlViewReady: false,
stateBundle: null,
workflowStatus: null,
controlView: null,
error: null,
});
assert.equal(typeof createIniPanelShellViewModel().controlPage.createController, "function"); assert.equal(typeof createIniPanelShellViewModel().controlPage.createController, "function");
assert.equal(createIniPanelShellViewModel().controlPage.loadSessionState, loadMachineSessionForControlPageWorkflow);
assert.equal(createIniPanelShellViewModel().controlPage.refresh, refreshMachineSessionControlPage); assert.equal(createIniPanelShellViewModel().controlPage.refresh, refreshMachineSessionControlPage);
assert.equal(typeof createIniPanelShellViewModel().controlPage.renderView, "function"); assert.equal(typeof createIniPanelShellViewModel().controlPage.renderView, "function");

View File

@@ -6,10 +6,12 @@ import {
createIniPanelLauncherLinkViewModel, createIniPanelLauncherLinkViewModel,
createIniPanelShellViewModel, createIniPanelShellViewModel,
createControlPageRefreshState, createControlPageRefreshState,
createControlPageSessionLoadState,
createMachineSessionControlPageController, createMachineSessionControlPageController,
getIniPanelEntryByHref, getIniPanelEntryByHref,
getIniPanelEntryManifest, getIniPanelEntryManifest,
getVisibleIniPanelEntries, getVisibleIniPanelEntries,
loadMachineSessionForControlPageWorkflow,
refreshMachineSessionControlPage, refreshMachineSessionControlPage,
renderMachineSessionControlView, renderMachineSessionControlView,
} from "../../../runtime/ui/ini-panel/ui-shell.js"; } from "../../../runtime/ui/ini-panel/ui-shell.js";
@@ -24,8 +26,19 @@ assert.deepEqual(createControlPageRefreshState({ phase: "waiting", statusText: "
renderResult: null, renderResult: null,
error: null, error: null,
}); });
assert.deepEqual(createControlPageSessionLoadState({ phase: "waiting-panel" }), {
phase: "waiting-panel",
panelReady: false,
apiReady: false,
controlViewReady: false,
stateBundle: null,
workflowStatus: null,
controlView: null,
error: null,
});
assert.equal(getIniPanelEntryByHref("./index.html")?.id, "edit-run"); assert.equal(getIniPanelEntryByHref("./index.html")?.id, "edit-run");
assert.equal(typeof createMachineSessionControlPageController, "function"); assert.equal(typeof createMachineSessionControlPageController, "function");
assert.equal(typeof loadMachineSessionForControlPageWorkflow, "function");
assert.equal(typeof refreshMachineSessionControlPage, "function"); assert.equal(typeof refreshMachineSessionControlPage, "function");
assert.equal(typeof renderMachineSessionControlView, "function"); assert.equal(typeof renderMachineSessionControlView, "function");
@@ -65,6 +78,7 @@ assert.deepEqual(shellViewModel.pages, [
]); ]);
assert.deepEqual(shellViewModel.launcherLinks, createIniPanelLauncherLinkViewModel()); assert.deepEqual(shellViewModel.launcherLinks, createIniPanelLauncherLinkViewModel());
assert.equal(shellViewModel.controlPage.createController, createMachineSessionControlPageController); assert.equal(shellViewModel.controlPage.createController, createMachineSessionControlPageController);
assert.equal(shellViewModel.controlPage.loadSessionState, loadMachineSessionForControlPageWorkflow);
assert.equal(shellViewModel.controlPage.refresh, refreshMachineSessionControlPage); assert.equal(shellViewModel.controlPage.refresh, refreshMachineSessionControlPage);
assert.equal(shellViewModel.controlPage.renderView, renderMachineSessionControlView); assert.equal(shellViewModel.controlPage.renderView, renderMachineSessionControlView);
shellViewModel.pages[0].title = "changed"; shellViewModel.pages[0].title = "changed";
@@ -102,6 +116,7 @@ const controller = createMachineSessionControlPageController({
assert.deepEqual(controller.render(), { rendered: true }); assert.deepEqual(controller.render(), { rendered: true });
assert.equal(statusNode.textContent, "run-gcode | ok | -"); assert.equal(statusNode.textContent, "run-gcode | ok | -");
assert.equal(renderedView.status.runStatus, "ok"); assert.equal(renderedView.status.runStatus, "ok");
assert.equal(controller.loadSessionState().phase, "ready");
assert.equal(controller.refresh().phase, "rendered"); assert.equal(controller.refresh().phase, "rendered");
console.log("ini_panel_ui_shell_node_smoke=ok"); console.log("ini_panel_ui_shell_node_smoke=ok");

View File

@@ -11,6 +11,7 @@ ROOT_DIR="$(cd "$(dirname "$0")/../../.." && pwd)"
"$ROOT_DIR/tests/ui/node/verify_ini_panel_state_summary.sh" "$ROOT_DIR/tests/ui/node/verify_ini_panel_state_summary.sh"
"$ROOT_DIR/tests/ui/node/verify_ini_panel_control_page_controller.sh" "$ROOT_DIR/tests/ui/node/verify_ini_panel_control_page_controller.sh"
"$ROOT_DIR/tests/ui/node/verify_ini_panel_control_page_refresh_workflow.sh" "$ROOT_DIR/tests/ui/node/verify_ini_panel_control_page_refresh_workflow.sh"
"$ROOT_DIR/tests/ui/node/verify_ini_panel_control_page_session_workflow.sh"
"$ROOT_DIR/tests/ui/node/verify_ini_panel_entry_manifest.sh" "$ROOT_DIR/tests/ui/node/verify_ini_panel_entry_manifest.sh"
"$ROOT_DIR/tests/ui/node/verify_ini_panel_ui_shell.sh" "$ROOT_DIR/tests/ui/node/verify_ini_panel_ui_shell.sh"
"$ROOT_DIR/tests/ui/node/verify_ini_panel_entry_docs.sh" "$ROOT_DIR/tests/ui/node/verify_ini_panel_entry_docs.sh"