Compare commits
6 Commits
ac463453da
...
a883af2d3e
| Author | SHA1 | Date | |
|---|---|---|---|
| a883af2d3e | |||
| cb6ef7824c | |||
| 98d236250f | |||
| ed5d03ea1b | |||
| 213514f64c | |||
| b330f7b1bc |
264
text11.txt
264
text11.txt
@@ -1633,3 +1633,267 @@ host_wasm_opfs_browser_smokes=ok
|
|||||||
继续推进实质 UI/API surface。下一批建议把 `ui-shell.js` 中用于 launcher 的入口列表再封装成
|
继续推进实质 UI/API surface。下一批建议把 `ui-shell.js` 中用于 launcher 的入口列表再封装成
|
||||||
`getVisibleIniPanelEntries()`,让页面不需要知道 manifest 复制语义;仍保持只读,不新增控制
|
`getVisibleIniPanelEntries()`,让页面不需要知道 manifest 复制语义;仍保持只读,不新增控制
|
||||||
按钮,不解析 G-code。
|
按钮,不解析 G-code。
|
||||||
|
|
||||||
|
二十一、2026-06-14 继续执行记录:INI panel visible entries helper
|
||||||
|
|
||||||
|
本轮按“加快实质性推进”在 `ui-shell.js` 增加 launcher-facing 的可见入口 helper,让页面和
|
||||||
|
文档使用 `getVisibleIniPanelEntries()`,不再直接依赖 manifest 复制语义。
|
||||||
|
|
||||||
|
完成内容:
|
||||||
|
|
||||||
|
- `wasm-port/runtime/ui/ini-panel/ui-shell.js` 新增 `getVisibleIniPanelEntries()`;
|
||||||
|
- `getVisibleIniPanelEntries()` 返回 launcher 可见入口列表;
|
||||||
|
- `wasm-port/runtime/ui/ini-panel/launch.html` 改为调用 `getVisibleIniPanelEntries()`;
|
||||||
|
- `wasm-port/tests/browser/ini_panel_launch_smoke.html` 改为验证 visible entries;
|
||||||
|
- `wasm-port/tests/ui/node/verify_ini_panel_ui_shell.mjs` 覆盖 visible entries 与复制语义;
|
||||||
|
- `wasm-port/docs/panel-entry.md` 改为以 `getVisibleIniPanelEntries()` 作为入口列表 API;
|
||||||
|
- `wasm-port/tests/ui/node/verify_ini_panel_entry_docs.mjs` 同步验证文档和 visible entries;
|
||||||
|
- 未新增控制按钮;
|
||||||
|
- 未解析 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_ui_shell.sh
|
||||||
|
wasm-port/tests/ui/node/verify_ini_panel_entry_docs.sh
|
||||||
|
wasm-port/tests/ui/node/verify_ui_node_smokes.sh
|
||||||
|
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_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
|
||||||
|
host_wasm_opfs_browser_smokes=ok
|
||||||
|
```
|
||||||
|
|
||||||
|
下一步建议:
|
||||||
|
|
||||||
|
继续推进 UI/API surface,但避免纯文档循环。下一批建议把 `getVisibleIniPanelEntries()` 用到
|
||||||
|
更多外层 shell 消费示例,或新增一个小型 Node helper 生成 launcher link view-model;仍保持
|
||||||
|
只读,不新增控制按钮,不解析 G-code。
|
||||||
|
|
||||||
|
二十二、2026-06-15 继续执行记录:INI panel launcher link view-model helper
|
||||||
|
|
||||||
|
本轮按“加快实质性推进”继续推进 UI/API surface,在 shell 层新增 launcher link
|
||||||
|
view-model helper,让 launcher 页面不直接读取 manifest 字段名。
|
||||||
|
|
||||||
|
完成内容:
|
||||||
|
|
||||||
|
- `wasm-port/runtime/ui/ini-panel/ui-shell.js` 新增
|
||||||
|
`createIniPanelLauncherLinkViewModel()`;
|
||||||
|
- launcher link view-model 输出 `entryId`、`href`、`label`,作为外层 shell 可直接渲染的
|
||||||
|
链接模型;
|
||||||
|
- `wasm-port/runtime/ui/ini-panel/launch.html` 改为调用
|
||||||
|
`createIniPanelLauncherLinkViewModel()` 渲染链接;
|
||||||
|
- `wasm-port/tests/browser/ini_panel_launch_smoke.html` 覆盖 launcher link view-model;
|
||||||
|
- `wasm-port/tests/ui/node/verify_ini_panel_ui_shell.mjs` 覆盖 view-model 内容与复制语义;
|
||||||
|
- `wasm-port/tests/ui/node/verify_ini_panel_entry_docs.mjs` 同步校验文档和 shell helper;
|
||||||
|
- `wasm-port/docs/panel-entry.md` 记录 launcher link view-model 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_ui_shell.sh
|
||||||
|
wasm-port/tests/ui/node/verify_ini_panel_entry_docs.sh
|
||||||
|
wasm-port/tests/browser/verify_ini_panel_browser.sh
|
||||||
|
wasm-port/tests/ui/node/verify_ui_node_smokes.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
|
||||||
|
SKIP_INI_BUILD=1 SKIP_INTERP_BUILD=1 wasm-port/tests/browser/verify_ini_panel_browser.sh
|
||||||
|
wasm-port/tests/host/verify_host_smokes.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
关键输出:
|
||||||
|
|
||||||
|
```text
|
||||||
|
ini_panel_ui_shell_node_smoke=ok
|
||||||
|
ini_panel_entry_docs_node_smoke=ok
|
||||||
|
browser_ini_opfs_smoke=ok
|
||||||
|
browser_ini_control_page_smoke=ok
|
||||||
|
browser_ini_launch_smoke=ok
|
||||||
|
ui_node_smokes=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
|
||||||
|
```
|
||||||
|
|
||||||
|
下一步建议:
|
||||||
|
|
||||||
|
继续推进 UI/API surface 的可复用边界。下一批建议新增一个
|
||||||
|
`createIniPanelShellViewModel()`,把 launcher links、可用页面和 control-page helper 暴露为
|
||||||
|
单一 shell model,供外层应用一次性挂载;仍保持只读,不新增控制按钮,不解析 G-code。
|
||||||
|
|
||||||
|
二十三、2026-06-15 继续执行记录:INI panel shell view-model helper
|
||||||
|
|
||||||
|
本轮按“第一原则:加快实质性推进”继续推进 UI/API surface,新增单一 shell view-model,
|
||||||
|
让外层应用可以一次性读取 launcher links、页面列表和 control-page 挂载 helper。
|
||||||
|
|
||||||
|
完成内容:
|
||||||
|
|
||||||
|
- `wasm-port/runtime/ui/ini-panel/ui-shell.js` 新增 `createIniPanelShellViewModel()`;
|
||||||
|
- shell view-model 返回:
|
||||||
|
- `pages`:只读页面列表;
|
||||||
|
- `launcherLinks`:可直接渲染的 launcher link view-model;
|
||||||
|
- `controlPage.createController`:现有 read-only control-page controller;
|
||||||
|
- `controlPage.renderView`:现有 read-only control-view renderer;
|
||||||
|
- `wasm-port/tests/ui/node/verify_ini_panel_ui_shell.mjs` 覆盖 shell view-model 结构、
|
||||||
|
helper 引用和复制语义;
|
||||||
|
- `wasm-port/tests/ui/node/verify_ini_panel_entry_docs.mjs` 同步校验文档和 shell helper;
|
||||||
|
- `wasm-port/docs/panel-entry.md` 记录 `createIniPanelShellViewModel()`;
|
||||||
|
- 未新增控制按钮;
|
||||||
|
- 未解析 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_ui_shell.sh
|
||||||
|
wasm-port/tests/ui/node/verify_ini_panel_entry_docs.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/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_entry_docs_node_smoke=ok
|
||||||
|
browser_ini_opfs_smoke=ok
|
||||||
|
browser_ini_control_page_smoke=ok
|
||||||
|
browser_ini_launch_smoke=ok
|
||||||
|
ui_node_smokes=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 或 browser/UI workflow 的实质链路。下一批建议在
|
||||||
|
`app.js` 外抽出一个 control-page snapshot refresh workflow helper,让 read-only 页面能复用
|
||||||
|
明确的 refresh 状态模型;仍保持只读,不新增控制按钮,不解析 G-code。
|
||||||
|
|
||||||
|
二十四、2026-06-15 继续执行记录:INI panel control-page refresh workflow helper
|
||||||
|
|
||||||
|
本轮按“第一原则:加快实质性推进”推进 browser/UI workflow,在 `app.js` 外新增
|
||||||
|
read-only control-page refresh workflow helper,让外层页面可复用明确的 refresh 状态模型。
|
||||||
|
|
||||||
|
完成内容:
|
||||||
|
|
||||||
|
- 新增 `wasm-port/runtime/ui/ini-panel/control-page-refresh-workflow.js`;
|
||||||
|
- 新增 `controlPageStatusText(status)`,格式化已观察到的 control view status 字段;
|
||||||
|
- 新增 `createControlPageRefreshState(input)`,固定 refresh 状态结构;
|
||||||
|
- 新增 `refreshMachineSessionControlPage(input)`,统一处理:
|
||||||
|
- `waiting`:panel API 尚不可用;
|
||||||
|
- `rendered`:读取 control view 并调用 renderer;
|
||||||
|
- `error`:control view getter 或 renderer 抛错;
|
||||||
|
- `createMachineSessionControlPageController()` 改为通过 refresh workflow 执行 render;
|
||||||
|
- control-page controller 新增 `refresh()`,保留原 `render()` 返回值兼容;
|
||||||
|
- `control-page.js` 对浏览器页面暴露 `refreshControlPage`;
|
||||||
|
- `ui-shell.js` re-export refresh workflow helper,并在 `createIniPanelShellViewModel()` 中暴露
|
||||||
|
`controlPage.refresh`;
|
||||||
|
- 新增 `wasm-port/tests/ui/node/verify_ini_panel_control_page_refresh_workflow.mjs` 和 `.sh`;
|
||||||
|
- `verify_ui_node_smokes.sh` 纳入 refresh workflow smoke;
|
||||||
|
- browser control-page smoke 验证 `refreshControlPage().phase === "rendered"`;
|
||||||
|
- `panel-entry.md` 和 entry docs gate 同步记录 refresh 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_refresh_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_refresh_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 的实质链路。下一批建议抽出
|
||||||
|
`loadMachineSessionForControlPageWorkflow()` 或等价 helper,让 read-only control page 能以明确
|
||||||
|
状态模型描述 embedded panel API readiness 和首帧 session state;仍保持只读,不新增控制按钮,
|
||||||
|
不解析 G-code。
|
||||||
|
|||||||
436
text12.txt
Normal file
436
text12.txt
Normal file
@@ -0,0 +1,436 @@
|
|||||||
|
项目整体完成情况与后续执行计划
|
||||||
|
|
||||||
|
生成时间: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 行为。
|
||||||
|
|
||||||
|
二、2026-06-15 继续执行记录:INI panel load-session workflow helper
|
||||||
|
|
||||||
|
本轮按“第一原则:加快实质性推进”继续推进 OPFS/session persistence 主链路,抽出
|
||||||
|
`load-session` 按钮路径的可复用 workflow helper,统一返回 loaded session、load summary、
|
||||||
|
field state 和 log lines。
|
||||||
|
|
||||||
|
完成内容:
|
||||||
|
|
||||||
|
- `wasm-port/runtime/ui/ini-panel/session-workflow.js` 新增
|
||||||
|
`createMachineSessionLoadFieldState(summary)`;
|
||||||
|
- `session-workflow.js` 新增 `machineSessionLoadWorkflowLogLines(summary)`;
|
||||||
|
- `session-workflow.js` 新增 `loadMachineSessionIntoWasmWorkflow({ loadSession })`;
|
||||||
|
- workflow 只调用外部传入的 LinuxCNC/OPFS session loader,并基于现有
|
||||||
|
`createMachineSessionLoadSummary()` 生成 UI 状态;
|
||||||
|
- `wasm-port/runtime/ui/ini-panel/app.js` 的 `load-session` handler 改为调用
|
||||||
|
`loadMachineSessionIntoWasmWorkflow()`;
|
||||||
|
- `app.js` 仍由 `loadMachineSessionFromOpfs()` 通过 LinuxCNC/OPFS bridge 执行实际 session
|
||||||
|
staging;
|
||||||
|
- `wasm-port/tests/ui/node/verify_ini_panel_session_workflow.mjs` 覆盖 load workflow 的
|
||||||
|
loader 调用、summary、field state、log lines 和缺失 loader 错误;
|
||||||
|
- 未新增控制按钮;
|
||||||
|
- 未解析 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_session_workflow.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_session_workflow_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 的实质链路。下一批建议将 `restore-load-session` 路径改为复用
|
||||||
|
`loadMachineSessionIntoWasmWorkflow()`,避免 restore+load 与单独 load 的 field/log 状态模型
|
||||||
|
分叉;仍保持由 LinuxCNC/OPFS bridge 执行实际语义,不在 JS 中解释 CNC 行为。
|
||||||
|
|
||||||
|
三、2026-06-15 继续执行记录:INI panel restore-load workflow 复用 load-session helper
|
||||||
|
|
||||||
|
本轮按“第一原则:加快实质性推进”继续推进 OPFS/session persistence 主链路,将
|
||||||
|
`restore-load-session` 路径收敛到已存在的 `loadMachineSessionIntoWasmWorkflow()`,让单独
|
||||||
|
load 与 restore+load 共享同一套 load summary、field state 和 log lines 模型。
|
||||||
|
|
||||||
|
完成内容:
|
||||||
|
|
||||||
|
- `restoreMachineSessionSnapshotWorkflow()` 的 restore+load 分支改为调用
|
||||||
|
`loadMachineSessionIntoWasmWorkflow({ loadSession })`;
|
||||||
|
- restore+load 返回的 `loadedSession`、`loadSummary` 和 INI/parameter/tool-table field state
|
||||||
|
直接来自 load workflow;
|
||||||
|
- restore+load 日志纳入 load workflow 的统一首行:
|
||||||
|
`Loaded machine session into the interpreter WASM filesystem.`;
|
||||||
|
- 删除 `app.js` 中已不再使用的 `sessionSnapshotSummaryLogLines` import;
|
||||||
|
- `verify_ini_panel_session_workflow.mjs` 增加 restore+load 复用断言,确认
|
||||||
|
`loadSummary` 与单独 load workflow 一致,且 `loadedSession` 保持 loader 返回对象;
|
||||||
|
- `ui-session-summary-helpers.md` 同步说明 restore-load 复用 load workflow;
|
||||||
|
- 未新增控制按钮;
|
||||||
|
- 未解析 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_session_workflow.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_session_workflow_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 的实质链路。下一批建议抽出 `run-session` 首帧 readiness
|
||||||
|
或 run 前置状态 helper,把“已加载 session、已选择/恢复 snapshot、默认 G-code 路径、run
|
||||||
|
按钮可执行条件”统一成可复用状态模型;仍保持只描述 UI/host boundary facts,不在 JS 中解释
|
||||||
|
G-code、canonical events 或 CNC 运行语义。
|
||||||
@@ -10,9 +10,15 @@ control view:
|
|||||||
```js
|
```js
|
||||||
import {
|
import {
|
||||||
INI_PANEL_ENTRIES,
|
INI_PANEL_ENTRIES,
|
||||||
|
createIniPanelLauncherLinkViewModel,
|
||||||
|
createIniPanelShellViewModel,
|
||||||
getIniPanelEntryByHref,
|
getIniPanelEntryByHref,
|
||||||
getIniPanelEntryManifest,
|
getVisibleIniPanelEntries,
|
||||||
createMachineSessionControlPageController,
|
createMachineSessionControlPageController,
|
||||||
|
loadMachineSessionForControlPageWorkflow,
|
||||||
|
createControlPageSessionLoadState,
|
||||||
|
refreshMachineSessionControlPage,
|
||||||
|
controlPageStatusText,
|
||||||
renderMachineSessionControlView,
|
renderMachineSessionControlView,
|
||||||
} from "../runtime/ui/ini-panel/ui-shell.js";
|
} from "../runtime/ui/ini-panel/ui-shell.js";
|
||||||
```
|
```
|
||||||
@@ -28,15 +34,38 @@ controller, and the control-view renderer. It does not implement CNC behavior.
|
|||||||
| Edit and run | `runtime/ui/ini-panel/index.html` | Editable INI panel and existing run workflow surface. |
|
| Edit and run | `runtime/ui/ini-panel/index.html` | Editable INI panel and existing run workflow surface. |
|
||||||
| Read-only control view | `runtime/ui/ini-panel/control-page.html` | Read-only control view backed by the panel API. |
|
| Read-only control view | `runtime/ui/ini-panel/control-page.html` | Read-only control view backed by the panel API. |
|
||||||
|
|
||||||
## Entry Manifest
|
## Visible Entries
|
||||||
|
|
||||||
The visible launch entries come from `getIniPanelEntryManifest()`:
|
The visible launch entries come from `getVisibleIniPanelEntries()`:
|
||||||
|
|
||||||
| Entry id | Href | Title |
|
| Entry id | Href | Title |
|
||||||
| --- | --- | --- |
|
| --- | --- | --- |
|
||||||
| `edit-run` | `./index.html` | Open edit and run panel |
|
| `edit-run` | `./index.html` | Open edit and run panel |
|
||||||
| `control-view` | `./control-page.html` | Open read-only control view |
|
| `control-view` | `./control-page.html` | Open read-only control view |
|
||||||
|
|
||||||
|
Launcher pages can render links from `createIniPanelLauncherLinkViewModel()`
|
||||||
|
without reading manifest field names directly. Each item has `entryId`, `href`,
|
||||||
|
and `label`.
|
||||||
|
|
||||||
|
Outer shells can call `createIniPanelShellViewModel()` when they need one
|
||||||
|
read-only model with `pages`, `launcherLinks`, and `controlPage` helpers for
|
||||||
|
mounting the existing control-page controller, refresh workflow, and renderer.
|
||||||
|
|
||||||
|
The control-page refresh workflow lives in
|
||||||
|
`runtime/ui/ini-panel/control-page-refresh-workflow.js`. Use
|
||||||
|
`refreshMachineSessionControlPage()` when an outer shell needs a structured
|
||||||
|
refresh result with `phase`, `statusText`, `view`, `renderResult`, and `error`.
|
||||||
|
`controlPageStatusText()` formats the already-observed panel status fields for
|
||||||
|
the read-only status line, and `createControlPageRefreshState()` is the stable
|
||||||
|
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
|
||||||
|
|||||||
@@ -9,8 +9,8 @@ for machine-session load state. These helpers are pure so browser UI panels,
|
|||||||
SDK-facing wrappers, and Node smoke tests can share the same data shape without
|
SDK-facing wrappers, and Node smoke tests can share the same data shape without
|
||||||
parsing log text.
|
parsing log text.
|
||||||
`runtime/ui/ini-panel/session-workflow.js` contains the reusable UI workflows
|
`runtime/ui/ini-panel/session-workflow.js` contains the reusable UI workflows
|
||||||
for saving, restoring, and restore-loading a machine session snapshot as
|
for saving, restoring, loading, and restore-loading a machine session snapshot
|
||||||
host-boundary operations.
|
as host-boundary operations.
|
||||||
|
|
||||||
The helpers do not implement CNC behavior. G-code execution, canonical events,
|
The helpers do not implement CNC behavior. G-code execution, canonical events,
|
||||||
tool handling, parameters, kinematics, and planner behavior remain owned by the
|
tool handling, parameters, kinematics, and planner behavior remain owned by the
|
||||||
@@ -76,7 +76,10 @@ The persistence functions are injected by the caller:
|
|||||||
|
|
||||||
The workflow loads the machine session snapshot, loads the persisted machine
|
The workflow loads the machine session snapshot, loads the persisted machine
|
||||||
text files so the editor can be restored, and optionally loads the machine
|
text files so the editor can be restored, and optionally loads the machine
|
||||||
session into the interpreter WASM filesystem.
|
session into the interpreter WASM filesystem. The optional restore-load path
|
||||||
|
reuses `loadMachineSessionIntoWasmWorkflow()` so standalone load and
|
||||||
|
restore-load consumers share the same load summary, field state, and load log
|
||||||
|
line model.
|
||||||
|
|
||||||
When `loadSession` is omitted, the returned `fields` contain the OPFS snapshot
|
When `loadSession` is omitted, the returned `fields` contain the OPFS snapshot
|
||||||
paths. When `loadSession` is supplied, the returned `fields` contain the loaded
|
paths. When `loadSession` is supplied, the returned `fields` contain the loaded
|
||||||
@@ -101,7 +104,8 @@ The returned object has this shape:
|
|||||||
loadedSession: { ... },
|
loadedSession: { ... },
|
||||||
logLines: [
|
logLines: [
|
||||||
"Snapshot: ui-machine-session/ui-machine-session.json",
|
"Snapshot: ui-machine-session/ui-machine-session.json",
|
||||||
"Workflow: save-session-snapshot"
|
"Workflow: save-session-snapshot",
|
||||||
|
"Loaded machine session into the interpreter WASM filesystem."
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -35,13 +35,7 @@ import {
|
|||||||
runGcodeSessionWorkflow,
|
runGcodeSessionWorkflow,
|
||||||
} from "./run-workflow.js";
|
} from "./run-workflow.js";
|
||||||
import {
|
import {
|
||||||
createMachineSessionLoadSummary,
|
loadMachineSessionIntoWasmWorkflow,
|
||||||
machineSessionLoadLogLines,
|
|
||||||
} from "./session-load-summary.js";
|
|
||||||
import {
|
|
||||||
sessionSnapshotSummaryLogLines,
|
|
||||||
} from "./session-summary.js";
|
|
||||||
import {
|
|
||||||
restoreMachineSessionSnapshotWorkflow,
|
restoreMachineSessionSnapshotWorkflow,
|
||||||
saveMachineSessionSnapshotWorkflow,
|
saveMachineSessionSnapshotWorkflow,
|
||||||
} from "./session-workflow.js";
|
} from "./session-workflow.js";
|
||||||
@@ -712,24 +706,17 @@ document.getElementById("restore-session-snapshot").addEventListener("click", as
|
|||||||
|
|
||||||
document.getElementById("load-session").addEventListener("click", async () => {
|
document.getElementById("load-session").addEventListener("click", async () => {
|
||||||
try {
|
try {
|
||||||
await loadMachineSessionIntoWasm();
|
const workflow = await loadMachineSessionIntoWasmWorkflow({
|
||||||
const loadSummary = createMachineSessionLoadSummary(loadedSession);
|
loadSession: () => loadMachineSessionIntoWasm(),
|
||||||
|
});
|
||||||
|
loadedSession = workflow.loadedSession;
|
||||||
updatePanelMachineSessionState({
|
updatePanelMachineSessionState({
|
||||||
sessionLoad: loadSummary,
|
sessionLoad: workflow.loadSummary,
|
||||||
fields: {
|
fields: workflow.fields,
|
||||||
sessionIni: loadSummary.iniWasmPath,
|
|
||||||
sessionParameters: loadSummary.parameterWasmPath,
|
|
||||||
sessionToolTable: loadSummary.toolTableWasmPath,
|
|
||||||
},
|
|
||||||
lastAction: "load-session",
|
lastAction: "load-session",
|
||||||
error: null,
|
error: null,
|
||||||
});
|
});
|
||||||
setLog(
|
setLog(workflow.logLines.join("\n"));
|
||||||
[
|
|
||||||
"Loaded machine session into the interpreter WASM filesystem.",
|
|
||||||
...machineSessionLoadLogLines(loadSummary),
|
|
||||||
].join("\n"),
|
|
||||||
);
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
updatePanelMachineSessionState({
|
updatePanelMachineSessionState({
|
||||||
lastAction: "load-session",
|
lastAction: "load-session",
|
||||||
|
|||||||
@@ -1,3 +1,10 @@
|
|||||||
|
import {
|
||||||
|
refreshMachineSessionControlPage,
|
||||||
|
} from "./control-page-refresh-workflow.js";
|
||||||
|
import {
|
||||||
|
loadMachineSessionForControlPageWorkflow,
|
||||||
|
} from "./control-page-session-workflow.js";
|
||||||
|
|
||||||
export function createMachineSessionControlPageController({
|
export function createMachineSessionControlPageController({
|
||||||
panelFrame,
|
panelFrame,
|
||||||
statusNode,
|
statusNode,
|
||||||
@@ -34,19 +41,24 @@ export function createMachineSessionControlPageController({
|
|||||||
}
|
}
|
||||||
|
|
||||||
function render() {
|
function render() {
|
||||||
const view = getControlView();
|
return refresh().renderResult;
|
||||||
if (!view) {
|
|
||||||
statusNode.textContent = "waiting for panel";
|
|
||||||
viewNode.textContent = "";
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
statusNode.textContent = [
|
function refresh() {
|
||||||
view.status.lastAction ?? "-",
|
return refreshMachineSessionControlPage({
|
||||||
view.status.runStatus ?? "-",
|
getControlView,
|
||||||
view.status.error ?? "-",
|
statusNode,
|
||||||
].join(" | ");
|
viewNode,
|
||||||
return renderControlView(viewNode, view);
|
renderControlView,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadSessionState() {
|
||||||
|
return loadMachineSessionForControlPageWorkflow({
|
||||||
|
getPanelWindow,
|
||||||
|
getPanelApi,
|
||||||
|
getControlView,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function startPolling() {
|
function startPolling() {
|
||||||
@@ -71,7 +83,9 @@ export function createMachineSessionControlPageController({
|
|||||||
getControlView,
|
getControlView,
|
||||||
getPanelApi,
|
getPanelApi,
|
||||||
getPanelWindow,
|
getPanelWindow,
|
||||||
|
loadSessionState,
|
||||||
mount,
|
mount,
|
||||||
|
refresh,
|
||||||
render,
|
render,
|
||||||
startPolling,
|
startPolling,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,89 @@
|
|||||||
|
export function controlPageStatusText(status = {}) {
|
||||||
|
return [
|
||||||
|
status.lastAction ?? "-",
|
||||||
|
status.runStatus ?? "-",
|
||||||
|
status.error ?? "-",
|
||||||
|
].join(" | ");
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createControlPageRefreshState({
|
||||||
|
phase,
|
||||||
|
statusText,
|
||||||
|
view = null,
|
||||||
|
renderResult = null,
|
||||||
|
error = null,
|
||||||
|
} = {}) {
|
||||||
|
return {
|
||||||
|
phase,
|
||||||
|
statusText,
|
||||||
|
view,
|
||||||
|
renderResult,
|
||||||
|
error,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function refreshMachineSessionControlPage({
|
||||||
|
getControlView,
|
||||||
|
statusNode,
|
||||||
|
viewNode,
|
||||||
|
renderControlView,
|
||||||
|
} = {}) {
|
||||||
|
if (typeof getControlView !== "function") {
|
||||||
|
throw new Error("A control view getter is required.");
|
||||||
|
}
|
||||||
|
if (!statusNode) {
|
||||||
|
throw new Error("A control page status node is required.");
|
||||||
|
}
|
||||||
|
if (!viewNode) {
|
||||||
|
throw new Error("A control page view node is required.");
|
||||||
|
}
|
||||||
|
if (typeof renderControlView !== "function") {
|
||||||
|
throw new Error("A control view renderer is required.");
|
||||||
|
}
|
||||||
|
|
||||||
|
let view = null;
|
||||||
|
try {
|
||||||
|
view = getControlView() ?? null;
|
||||||
|
} catch (error) {
|
||||||
|
const statusText = `refresh failed | - | ${error.message}`;
|
||||||
|
statusNode.textContent = statusText;
|
||||||
|
viewNode.textContent = "";
|
||||||
|
return createControlPageRefreshState({
|
||||||
|
phase: "error",
|
||||||
|
statusText,
|
||||||
|
error: error.message,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!view) {
|
||||||
|
const statusText = "waiting for panel";
|
||||||
|
statusNode.textContent = statusText;
|
||||||
|
viewNode.textContent = "";
|
||||||
|
return createControlPageRefreshState({
|
||||||
|
phase: "waiting",
|
||||||
|
statusText,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const statusText = controlPageStatusText(view.status);
|
||||||
|
statusNode.textContent = statusText;
|
||||||
|
try {
|
||||||
|
const renderResult = renderControlView(viewNode, view);
|
||||||
|
return createControlPageRefreshState({
|
||||||
|
phase: "rendered",
|
||||||
|
statusText,
|
||||||
|
view,
|
||||||
|
renderResult,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
const errorStatusText = `refresh failed | - | ${error.message}`;
|
||||||
|
statusNode.textContent = errorStatusText;
|
||||||
|
viewNode.textContent = "";
|
||||||
|
return createControlPageRefreshState({
|
||||||
|
phase: "error",
|
||||||
|
statusText: errorStatusText,
|
||||||
|
view,
|
||||||
|
error: error.message,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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,
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -21,5 +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,
|
||||||
renderControlPage: controller.render,
|
renderControlPage: controller.render,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -73,16 +73,16 @@
|
|||||||
</section>
|
</section>
|
||||||
</main>
|
</main>
|
||||||
<script type="module">
|
<script type="module">
|
||||||
import { getIniPanelEntryManifest } from "./ui-shell.js";
|
import { createIniPanelLauncherLinkViewModel } from "./ui-shell.js";
|
||||||
|
|
||||||
const manifest = getIniPanelEntryManifest();
|
const links = createIniPanelLauncherLinkViewModel();
|
||||||
for (const entry of manifest) {
|
for (const linkModel of links) {
|
||||||
const link = document.querySelector(`a[data-entry-id="${entry.id}"]`);
|
const link = document.querySelector(`a[data-entry-id="${linkModel.entryId}"]`);
|
||||||
if (!link) {
|
if (!link) {
|
||||||
throw new Error(`Missing launcher entry ${entry.id}`);
|
throw new Error(`Missing launcher entry ${linkModel.entryId}`);
|
||||||
}
|
}
|
||||||
link.textContent = entry.title;
|
link.textContent = linkModel.label;
|
||||||
link.href = entry.href;
|
link.href = linkModel.href;
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
@@ -37,6 +37,35 @@ export function sessionSnapshotRestoreLogLines(summary) {
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function createMachineSessionLoadFieldState(summary) {
|
||||||
|
return {
|
||||||
|
sessionIni: summary.iniWasmPath,
|
||||||
|
sessionParameters: summary.parameterWasmPath,
|
||||||
|
sessionToolTable: summary.toolTableWasmPath,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function machineSessionLoadWorkflowLogLines(summary) {
|
||||||
|
return [
|
||||||
|
"Loaded machine session into the interpreter WASM filesystem.",
|
||||||
|
...machineSessionLoadLogLines(summary),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function loadMachineSessionIntoWasmWorkflow({ loadSession }) {
|
||||||
|
if (typeof loadSession !== "function") {
|
||||||
|
throw new Error("A machine session loader is required.");
|
||||||
|
}
|
||||||
|
const loadedSession = await loadSession();
|
||||||
|
const loadSummary = createMachineSessionLoadSummary(loadedSession);
|
||||||
|
return {
|
||||||
|
loadedSession,
|
||||||
|
loadSummary,
|
||||||
|
fields: createMachineSessionLoadFieldState(loadSummary),
|
||||||
|
logLines: machineSessionLoadWorkflowLogLines(loadSummary),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export async function saveMachineSessionSnapshotWorkflow({
|
export async function saveMachineSessionSnapshotWorkflow({
|
||||||
machineId,
|
machineId,
|
||||||
snapshotId,
|
snapshotId,
|
||||||
@@ -105,20 +134,17 @@ export async function restoreMachineSessionSnapshotWorkflow({
|
|||||||
logLines: sessionSnapshotRestoreLogLines(summary),
|
logLines: sessionSnapshotRestoreLogLines(summary),
|
||||||
};
|
};
|
||||||
if (loadSession) {
|
if (loadSession) {
|
||||||
const loadedSession = await loadSession();
|
const loadWorkflow = await loadMachineSessionIntoWasmWorkflow({ loadSession });
|
||||||
const loadSummary = createMachineSessionLoadSummary(loadedSession);
|
result.loadedSession = loadWorkflow.loadedSession;
|
||||||
result.loadedSession = loadedSession;
|
result.loadSummary = loadWorkflow.loadSummary;
|
||||||
result.loadSummary = loadSummary;
|
|
||||||
result.fields = {
|
result.fields = {
|
||||||
...fields,
|
...fields,
|
||||||
sessionIni: loadSummary.iniWasmPath,
|
...loadWorkflow.fields,
|
||||||
sessionParameters: loadSummary.parameterWasmPath,
|
|
||||||
sessionToolTable: loadSummary.toolTableWasmPath,
|
|
||||||
sessionGcode: summary.gcodeOpfsPath,
|
sessionGcode: summary.gcodeOpfsPath,
|
||||||
};
|
};
|
||||||
result.logLines = [
|
result.logLines = [
|
||||||
...sessionSnapshotSummaryLogLines(summary),
|
...sessionSnapshotSummaryLogLines(summary),
|
||||||
...machineSessionLoadLogLines(loadSummary),
|
...loadWorkflow.logLines,
|
||||||
`G-code: ${summary.gcodeOpfsPath ?? "-"}`,
|
`G-code: ${summary.gcodeOpfsPath ?? "-"}`,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,32 @@
|
|||||||
|
import {
|
||||||
|
INI_PANEL_ENTRIES,
|
||||||
|
getIniPanelEntryByHref,
|
||||||
|
getIniPanelEntryManifest,
|
||||||
|
} from "./entry-manifest.js";
|
||||||
|
import {
|
||||||
|
createMachineSessionControlPageController,
|
||||||
|
} from "./control-page-controller.js";
|
||||||
|
import {
|
||||||
|
renderMachineSessionControlView,
|
||||||
|
} from "./control-view-renderer.js";
|
||||||
|
import {
|
||||||
|
controlPageStatusText,
|
||||||
|
createControlPageRefreshState,
|
||||||
|
refreshMachineSessionControlPage,
|
||||||
|
} from "./control-page-refresh-workflow.js";
|
||||||
|
import {
|
||||||
|
createControlPageSessionLoadState,
|
||||||
|
loadMachineSessionForControlPageWorkflow,
|
||||||
|
} from "./control-page-session-workflow.js";
|
||||||
|
export {
|
||||||
|
controlPageStatusText,
|
||||||
|
createControlPageRefreshState,
|
||||||
|
refreshMachineSessionControlPage,
|
||||||
|
} 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,
|
||||||
@@ -9,3 +38,32 @@ export {
|
|||||||
export {
|
export {
|
||||||
renderMachineSessionControlView,
|
renderMachineSessionControlView,
|
||||||
} from "./control-view-renderer.js";
|
} from "./control-view-renderer.js";
|
||||||
|
|
||||||
|
export function getVisibleIniPanelEntries() {
|
||||||
|
return getIniPanelEntryManifest();
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createIniPanelLauncherLinkViewModel(entries = getVisibleIniPanelEntries()) {
|
||||||
|
return entries.map(({ id, href, title }) => ({
|
||||||
|
entryId: id,
|
||||||
|
href,
|
||||||
|
label: title,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createIniPanelShellViewModel(entries = getVisibleIniPanelEntries()) {
|
||||||
|
return {
|
||||||
|
pages: entries.map(({ id, href, title }) => ({
|
||||||
|
id,
|
||||||
|
href,
|
||||||
|
title,
|
||||||
|
})),
|
||||||
|
launcherLinks: createIniPanelLauncherLinkViewModel(entries),
|
||||||
|
controlPage: {
|
||||||
|
createController: createMachineSessionControlPageController,
|
||||||
|
loadSessionState: loadMachineSessionForControlPageWorkflow,
|
||||||
|
refresh: refreshMachineSessionControlPage,
|
||||||
|
renderView: renderMachineSessionControlView,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|||||||
@@ -64,6 +64,8 @@ TOOL_TABLE = browser-tool.tbl
|
|||||||
if (
|
if (
|
||||||
!controlPageApi?.getControlView ||
|
!controlPageApi?.getControlView ||
|
||||||
!controlPageApi?.getPanelApi ||
|
!controlPageApi?.getPanelApi ||
|
||||||
|
!controlPageApi?.loadControlPageSessionState ||
|
||||||
|
!controlPageApi?.refreshControlPage ||
|
||||||
!controlPageApi?.renderControlPage
|
!controlPageApi?.renderControlPage
|
||||||
) {
|
) {
|
||||||
throw new Error("control page API namespace did not expose control helpers");
|
throw new Error("control page API namespace did not expose control helpers");
|
||||||
@@ -82,6 +84,16 @@ TOOL_TABLE = browser-tool.tbl
|
|||||||
() => controlPageApi.getControlView(),
|
() => controlPageApi.getControlView(),
|
||||||
"control page API view",
|
"control page API view",
|
||||||
);
|
);
|
||||||
|
assertEqual(
|
||||||
|
controlPageApi.refreshControlPage().phase,
|
||||||
|
"rendered",
|
||||||
|
"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();
|
||||||
|
|||||||
@@ -8,8 +8,9 @@
|
|||||||
<pre id="status">running</pre>
|
<pre id="status">running</pre>
|
||||||
<script type="module">
|
<script type="module">
|
||||||
import {
|
import {
|
||||||
|
createIniPanelLauncherLinkViewModel,
|
||||||
getIniPanelEntryByHref,
|
getIniPanelEntryByHref,
|
||||||
getIniPanelEntryManifest,
|
getVisibleIniPanelEntries,
|
||||||
} from "../../runtime/ui/ini-panel/ui-shell.js";
|
} from "../../runtime/ui/ini-panel/ui-shell.js";
|
||||||
|
|
||||||
function assertEqual(actual, expected, label) {
|
function assertEqual(actual, expected, label) {
|
||||||
@@ -30,8 +31,13 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const manifest = getIniPanelEntryManifest();
|
const visibleEntries = getVisibleIniPanelEntries();
|
||||||
assertEqual(manifest.length, 2, "launch manifest length");
|
assertEqual(visibleEntries.length, 2, "launch visible entry length");
|
||||||
|
const launcherLinks = createIniPanelLauncherLinkViewModel();
|
||||||
|
assertEqual(launcherLinks.length, 2, "launcher link model length");
|
||||||
|
assertEqual(launcherLinks[0].entryId, "edit-run", "launcher link entry id");
|
||||||
|
assertEqual(launcherLinks[0].label, "Open edit and run panel", "launcher link label");
|
||||||
|
assertEqual(launcherLinks[0].href, "./index.html", "launcher link href");
|
||||||
assertEqual(getIniPanelEntryByHref("./index.html").id, "edit-run", "launch manifest edit entry");
|
assertEqual(getIniPanelEntryByHref("./index.html").id, "edit-run", "launch manifest edit entry");
|
||||||
assertEqual(
|
assertEqual(
|
||||||
getIniPanelEntryByHref("./control-page.html").title,
|
getIniPanelEntryByHref("./control-page.html").title,
|
||||||
|
|||||||
@@ -3,6 +3,9 @@ import assert from "node:assert/strict";
|
|||||||
import {
|
import {
|
||||||
createMachineSessionControlPageController,
|
createMachineSessionControlPageController,
|
||||||
} from "../../../runtime/ui/ini-panel/control-page-controller.js";
|
} from "../../../runtime/ui/ini-panel/control-page-controller.js";
|
||||||
|
import {
|
||||||
|
controlPageStatusText,
|
||||||
|
} from "../../../runtime/ui/ini-panel/control-page-refresh-workflow.js";
|
||||||
|
|
||||||
function createPanelFrame(panelApi = null) {
|
function createPanelFrame(panelApi = null) {
|
||||||
const listeners = new Map();
|
const listeners = new Map();
|
||||||
@@ -36,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();
|
||||||
@@ -66,15 +80,30 @@ 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 | -");
|
||||||
assert.deepEqual(rendered, [{ container: viewNode, view: expectedView }]);
|
assert.deepEqual(rendered, [{ container: viewNode, view: expectedView }]);
|
||||||
|
const refreshResult = controller.refresh();
|
||||||
|
assert.equal(refreshResult.phase, "rendered");
|
||||||
|
assert.equal(refreshResult.statusText, controlPageStatusText(expectedView.status));
|
||||||
|
|
||||||
const mountedFrame = createPanelFrame(panelApi);
|
const mountedFrame = createPanelFrame(panelApi);
|
||||||
const mountedStatusNode = { textContent: "" };
|
const mountedStatusNode = { textContent: "" };
|
||||||
|
|||||||
@@ -0,0 +1,126 @@
|
|||||||
|
import assert from "node:assert/strict";
|
||||||
|
|
||||||
|
import {
|
||||||
|
controlPageStatusText,
|
||||||
|
createControlPageRefreshState,
|
||||||
|
refreshMachineSessionControlPage,
|
||||||
|
} from "../../../runtime/ui/ini-panel/control-page-refresh-workflow.js";
|
||||||
|
|
||||||
|
const expectedView = {
|
||||||
|
status: {
|
||||||
|
lastAction: "run-gcode",
|
||||||
|
runStatus: "ok",
|
||||||
|
error: null,
|
||||||
|
},
|
||||||
|
sections: [
|
||||||
|
{
|
||||||
|
id: "run",
|
||||||
|
rows: [
|
||||||
|
{ label: "Canonical events", value: 2 },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
assert.equal(controlPageStatusText(expectedView.status), "run-gcode | ok | -");
|
||||||
|
assert.deepEqual(createControlPageRefreshState({ phase: "waiting", statusText: "waiting for panel" }), {
|
||||||
|
phase: "waiting",
|
||||||
|
statusText: "waiting for panel",
|
||||||
|
view: null,
|
||||||
|
renderResult: null,
|
||||||
|
error: null,
|
||||||
|
});
|
||||||
|
|
||||||
|
const waitingStatusNode = { textContent: "" };
|
||||||
|
const waitingViewNode = { textContent: "previous" };
|
||||||
|
assert.deepEqual(
|
||||||
|
refreshMachineSessionControlPage({
|
||||||
|
getControlView: () => null,
|
||||||
|
statusNode: waitingStatusNode,
|
||||||
|
viewNode: waitingViewNode,
|
||||||
|
renderControlView: () => "unused",
|
||||||
|
}),
|
||||||
|
{
|
||||||
|
phase: "waiting",
|
||||||
|
statusText: "waiting for panel",
|
||||||
|
view: null,
|
||||||
|
renderResult: null,
|
||||||
|
error: null,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
assert.equal(waitingStatusNode.textContent, "waiting for panel");
|
||||||
|
assert.equal(waitingViewNode.textContent, "");
|
||||||
|
|
||||||
|
const renderedStatusNode = { textContent: "" };
|
||||||
|
const renderedViewNode = { textContent: "" };
|
||||||
|
let renderedInput = null;
|
||||||
|
assert.deepEqual(
|
||||||
|
refreshMachineSessionControlPage({
|
||||||
|
getControlView: () => expectedView,
|
||||||
|
statusNode: renderedStatusNode,
|
||||||
|
viewNode: renderedViewNode,
|
||||||
|
renderControlView: (container, view) => {
|
||||||
|
renderedInput = { container, view };
|
||||||
|
return { rendered: true };
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
{
|
||||||
|
phase: "rendered",
|
||||||
|
statusText: "run-gcode | ok | -",
|
||||||
|
view: expectedView,
|
||||||
|
renderResult: { rendered: true },
|
||||||
|
error: null,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
assert.deepEqual(renderedInput, { container: renderedViewNode, view: expectedView });
|
||||||
|
assert.equal(renderedStatusNode.textContent, "run-gcode | ok | -");
|
||||||
|
|
||||||
|
const getterErrorStatusNode = { textContent: "" };
|
||||||
|
const getterErrorViewNode = { textContent: "previous" };
|
||||||
|
assert.deepEqual(
|
||||||
|
refreshMachineSessionControlPage({
|
||||||
|
getControlView: () => {
|
||||||
|
throw new Error("panel unavailable");
|
||||||
|
},
|
||||||
|
statusNode: getterErrorStatusNode,
|
||||||
|
viewNode: getterErrorViewNode,
|
||||||
|
renderControlView: () => "unused",
|
||||||
|
}),
|
||||||
|
{
|
||||||
|
phase: "error",
|
||||||
|
statusText: "refresh failed | - | panel unavailable",
|
||||||
|
view: null,
|
||||||
|
renderResult: null,
|
||||||
|
error: "panel unavailable",
|
||||||
|
},
|
||||||
|
);
|
||||||
|
assert.equal(getterErrorViewNode.textContent, "");
|
||||||
|
|
||||||
|
const renderErrorStatusNode = { textContent: "" };
|
||||||
|
const renderErrorViewNode = { textContent: "previous" };
|
||||||
|
assert.deepEqual(
|
||||||
|
refreshMachineSessionControlPage({
|
||||||
|
getControlView: () => expectedView,
|
||||||
|
statusNode: renderErrorStatusNode,
|
||||||
|
viewNode: renderErrorViewNode,
|
||||||
|
renderControlView: () => {
|
||||||
|
throw new Error("renderer unavailable");
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
{
|
||||||
|
phase: "error",
|
||||||
|
statusText: "refresh failed | - | renderer unavailable",
|
||||||
|
view: expectedView,
|
||||||
|
renderResult: null,
|
||||||
|
error: "renderer unavailable",
|
||||||
|
},
|
||||||
|
);
|
||||||
|
assert.equal(renderErrorStatusNode.textContent, "refresh failed | - | renderer unavailable");
|
||||||
|
assert.equal(renderErrorViewNode.textContent, "");
|
||||||
|
|
||||||
|
assert.throws(
|
||||||
|
() => refreshMachineSessionControlPage(),
|
||||||
|
/control view getter/,
|
||||||
|
);
|
||||||
|
|
||||||
|
console.log("ini_panel_control_page_refresh_workflow_node_smoke=ok");
|
||||||
@@ -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_refresh_workflow.mjs"
|
||||||
@@ -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");
|
||||||
@@ -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"
|
||||||
@@ -5,19 +5,39 @@ import { dirname, resolve } from "node:path";
|
|||||||
|
|
||||||
import {
|
import {
|
||||||
INI_PANEL_ENTRIES,
|
INI_PANEL_ENTRIES,
|
||||||
getIniPanelEntryManifest,
|
controlPageStatusText,
|
||||||
|
createControlPageSessionLoadState,
|
||||||
|
createIniPanelLauncherLinkViewModel,
|
||||||
|
createIniPanelShellViewModel,
|
||||||
|
loadMachineSessionForControlPageWorkflow,
|
||||||
|
refreshMachineSessionControlPage,
|
||||||
|
getVisibleIniPanelEntries,
|
||||||
} from "../../../runtime/ui/ini-panel/ui-shell.js";
|
} from "../../../runtime/ui/ini-panel/ui-shell.js";
|
||||||
|
|
||||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||||
const root = resolve(__dirname, "../../..");
|
const root = resolve(__dirname, "../../..");
|
||||||
const docText = readFileSync(resolve(root, "docs/panel-entry.md"), "utf8");
|
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 refreshWorkflowText = readFileSync(
|
||||||
|
resolve(root, "runtime/ui/ini-panel/control-page-refresh-workflow.js"),
|
||||||
|
"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",
|
||||||
|
"createControlPageSessionLoadState",
|
||||||
|
"createIniPanelLauncherLinkViewModel",
|
||||||
|
"createIniPanelShellViewModel",
|
||||||
"getIniPanelEntryByHref",
|
"getIniPanelEntryByHref",
|
||||||
"getIniPanelEntryManifest",
|
"getVisibleIniPanelEntries",
|
||||||
"createMachineSessionControlPageController",
|
"createMachineSessionControlPageController",
|
||||||
|
"loadMachineSessionForControlPageWorkflow",
|
||||||
|
"refreshMachineSessionControlPage",
|
||||||
"renderMachineSessionControlView",
|
"renderMachineSessionControlView",
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -26,6 +46,23 @@ for (const exportName of requiredShellExports) {
|
|||||||
assert.match(docText, new RegExp(`\\b${exportName}\\b`));
|
assert.match(docText, new RegExp(`\\b${exportName}\\b`));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (const exportName of [
|
||||||
|
"controlPageStatusText",
|
||||||
|
"createControlPageRefreshState",
|
||||||
|
"refreshMachineSessionControlPage",
|
||||||
|
]) {
|
||||||
|
assert.match(refreshWorkflowText, new RegExp(`\\bexport function ${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"],
|
||||||
@@ -36,7 +73,32 @@ for (const [label, path] of requiredPages) {
|
|||||||
assert.match(docText, new RegExp(`\\| ${label} \\| \`${path.replaceAll("/", "\\/")}\``));
|
assert.match(docText, new RegExp(`\\| ${label} \\| \`${path.replaceAll("/", "\\/")}\``));
|
||||||
}
|
}
|
||||||
|
|
||||||
assert.deepEqual(getIniPanelEntryManifest(), INI_PANEL_ENTRIES);
|
assert.deepEqual(getVisibleIniPanelEntries(), INI_PANEL_ENTRIES);
|
||||||
|
assert.deepEqual(
|
||||||
|
createIniPanelLauncherLinkViewModel(),
|
||||||
|
INI_PANEL_ENTRIES.map(({ id, href, title }) => ({
|
||||||
|
entryId: id,
|
||||||
|
href,
|
||||||
|
label: title,
|
||||||
|
})),
|
||||||
|
);
|
||||||
|
assert.deepEqual(createIniPanelShellViewModel().pages, getVisibleIniPanelEntries());
|
||||||
|
assert.deepEqual(createIniPanelShellViewModel().launcherLinks, createIniPanelLauncherLinkViewModel());
|
||||||
|
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(createIniPanelShellViewModel().controlPage.loadSessionState, loadMachineSessionForControlPageWorkflow);
|
||||||
|
assert.equal(createIniPanelShellViewModel().controlPage.refresh, refreshMachineSessionControlPage);
|
||||||
|
assert.equal(typeof createIniPanelShellViewModel().controlPage.renderView, "function");
|
||||||
|
|
||||||
for (const entry of INI_PANEL_ENTRIES) {
|
for (const entry of INI_PANEL_ENTRIES) {
|
||||||
const escapedHref = entry.href.replace(".", "\\.");
|
const escapedHref = entry.href.replace(".", "\\.");
|
||||||
|
|||||||
@@ -1,10 +1,68 @@
|
|||||||
import assert from "node:assert/strict";
|
import assert from "node:assert/strict";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
createMachineSessionLoadFieldState,
|
||||||
|
loadMachineSessionIntoWasmWorkflow,
|
||||||
|
machineSessionLoadWorkflowLogLines,
|
||||||
restoreMachineSessionSnapshotWorkflow,
|
restoreMachineSessionSnapshotWorkflow,
|
||||||
saveMachineSessionSnapshotWorkflow,
|
saveMachineSessionSnapshotWorkflow,
|
||||||
} from "../../../runtime/ui/ini-panel/session-workflow.js";
|
} from "../../../runtime/ui/ini-panel/session-workflow.js";
|
||||||
|
|
||||||
|
const loadedSessionFixture = {
|
||||||
|
ini: {
|
||||||
|
opfsPath: "linuxcnc/machines/xyzab-tdr/machine.ini",
|
||||||
|
wasmPath: "/work/session-machine.ini",
|
||||||
|
},
|
||||||
|
parameters: {
|
||||||
|
opfsPath: "linuxcnc/machines/xyzab-tdr/linuxcnc.var",
|
||||||
|
wasmPath: "/work/session-linuxcnc.var",
|
||||||
|
result: "restore_parameters=0",
|
||||||
|
},
|
||||||
|
toolTable: {
|
||||||
|
opfsPath: "linuxcnc/machines/xyzab-tdr/tool.tbl",
|
||||||
|
wasmPath: "/work/session-tool.tbl",
|
||||||
|
result: "tooldata_load=0",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const loadWorkflowCalls = [];
|
||||||
|
const loadWorkflow = await loadMachineSessionIntoWasmWorkflow({
|
||||||
|
loadSession: async () => {
|
||||||
|
loadWorkflowCalls.push(["loadSession"]);
|
||||||
|
return loadedSessionFixture;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.deepEqual(loadWorkflowCalls, [["loadSession"]]);
|
||||||
|
assert.deepEqual(loadWorkflow.loadSummary, {
|
||||||
|
iniOpfsPath: "linuxcnc/machines/xyzab-tdr/machine.ini",
|
||||||
|
iniWasmPath: "/work/session-machine.ini",
|
||||||
|
parameterOpfsPath: "linuxcnc/machines/xyzab-tdr/linuxcnc.var",
|
||||||
|
parameterWasmPath: "/work/session-linuxcnc.var",
|
||||||
|
parameterResult: "restore_parameters=0",
|
||||||
|
toolTableOpfsPath: "linuxcnc/machines/xyzab-tdr/tool.tbl",
|
||||||
|
toolTableWasmPath: "/work/session-tool.tbl",
|
||||||
|
toolTableResult: "tooldata_load=0",
|
||||||
|
});
|
||||||
|
assert.deepEqual(loadWorkflow.fields, {
|
||||||
|
sessionIni: "/work/session-machine.ini",
|
||||||
|
sessionParameters: "/work/session-linuxcnc.var",
|
||||||
|
sessionToolTable: "/work/session-tool.tbl",
|
||||||
|
});
|
||||||
|
assert.deepEqual(createMachineSessionLoadFieldState(loadWorkflow.loadSummary), loadWorkflow.fields);
|
||||||
|
assert.deepEqual(machineSessionLoadWorkflowLogLines(loadWorkflow.loadSummary), [
|
||||||
|
"Loaded machine session into the interpreter WASM filesystem.",
|
||||||
|
"INI: linuxcnc/machines/xyzab-tdr/machine.ini -> /work/session-machine.ini",
|
||||||
|
"Parameter file: linuxcnc/machines/xyzab-tdr/linuxcnc.var -> /work/session-linuxcnc.var",
|
||||||
|
"Parameters: restore_parameters=0",
|
||||||
|
"Tool table file: linuxcnc/machines/xyzab-tdr/tool.tbl -> /work/session-tool.tbl",
|
||||||
|
"Tool table: tooldata_load=0",
|
||||||
|
]);
|
||||||
|
await assert.rejects(
|
||||||
|
() => loadMachineSessionIntoWasmWorkflow({}),
|
||||||
|
/machine session loader/,
|
||||||
|
);
|
||||||
|
|
||||||
const calls = [];
|
const calls = [];
|
||||||
const saveWorkflow = await saveMachineSessionSnapshotWorkflow({
|
const saveWorkflow = await saveMachineSessionSnapshotWorkflow({
|
||||||
machineId: "xyzab-tdr",
|
machineId: "xyzab-tdr",
|
||||||
@@ -199,22 +257,7 @@ const restoreLoadWorkflow = await restoreMachineSessionSnapshotWorkflow({
|
|||||||
},
|
},
|
||||||
loadSession: async () => {
|
loadSession: async () => {
|
||||||
restoreLoadCalls.push(["loadSession"]);
|
restoreLoadCalls.push(["loadSession"]);
|
||||||
return {
|
return loadedSessionFixture;
|
||||||
ini: {
|
|
||||||
opfsPath: "linuxcnc/machines/xyzab-tdr/machine.ini",
|
|
||||||
wasmPath: "/work/session-machine.ini",
|
|
||||||
},
|
|
||||||
parameters: {
|
|
||||||
opfsPath: "linuxcnc/machines/xyzab-tdr/linuxcnc.var",
|
|
||||||
wasmPath: "/work/session-linuxcnc.var",
|
|
||||||
result: "restore_parameters=0",
|
|
||||||
},
|
|
||||||
toolTable: {
|
|
||||||
opfsPath: "linuxcnc/machines/xyzab-tdr/tool.tbl",
|
|
||||||
wasmPath: "/work/session-tool.tbl",
|
|
||||||
result: "tooldata_load=0",
|
|
||||||
},
|
|
||||||
};
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -236,11 +279,14 @@ assert.deepEqual(restoreLoadWorkflow.fields, {
|
|||||||
sessionGcode: "linuxcnc/gcode/ui-session.ngc",
|
sessionGcode: "linuxcnc/gcode/ui-session.ngc",
|
||||||
sessionSnapshot: "ui-machine-session/ui-machine-session.json",
|
sessionSnapshot: "ui-machine-session/ui-machine-session.json",
|
||||||
});
|
});
|
||||||
|
assert.deepEqual(restoreLoadWorkflow.loadSummary, loadWorkflow.loadSummary);
|
||||||
|
assert.equal(restoreLoadWorkflow.loadedSession, loadedSessionFixture);
|
||||||
|
|
||||||
assert.deepEqual(restoreLoadWorkflow.logLines, [
|
assert.deepEqual(restoreLoadWorkflow.logLines, [
|
||||||
"Snapshot: ui-machine-session/ui-machine-session.json",
|
"Snapshot: ui-machine-session/ui-machine-session.json",
|
||||||
"Workflow: save-session-snapshot",
|
"Workflow: save-session-snapshot",
|
||||||
"Default G-code: linuxcnc/gcode/ui-session.ngc",
|
"Default G-code: linuxcnc/gcode/ui-session.ngc",
|
||||||
|
"Loaded machine session into the interpreter WASM filesystem.",
|
||||||
"INI: linuxcnc/machines/xyzab-tdr/machine.ini -> /work/session-machine.ini",
|
"INI: linuxcnc/machines/xyzab-tdr/machine.ini -> /work/session-machine.ini",
|
||||||
"Parameter file: linuxcnc/machines/xyzab-tdr/linuxcnc.var -> /work/session-linuxcnc.var",
|
"Parameter file: linuxcnc/machines/xyzab-tdr/linuxcnc.var -> /work/session-linuxcnc.var",
|
||||||
"Parameters: restore_parameters=0",
|
"Parameters: restore_parameters=0",
|
||||||
|
|||||||
@@ -2,20 +2,88 @@ import assert from "node:assert/strict";
|
|||||||
|
|
||||||
import {
|
import {
|
||||||
INI_PANEL_ENTRIES,
|
INI_PANEL_ENTRIES,
|
||||||
|
controlPageStatusText,
|
||||||
|
createIniPanelLauncherLinkViewModel,
|
||||||
|
createIniPanelShellViewModel,
|
||||||
|
createControlPageRefreshState,
|
||||||
|
createControlPageSessionLoadState,
|
||||||
createMachineSessionControlPageController,
|
createMachineSessionControlPageController,
|
||||||
getIniPanelEntryByHref,
|
getIniPanelEntryByHref,
|
||||||
getIniPanelEntryManifest,
|
getIniPanelEntryManifest,
|
||||||
|
getVisibleIniPanelEntries,
|
||||||
|
loadMachineSessionForControlPageWorkflow,
|
||||||
|
refreshMachineSessionControlPage,
|
||||||
renderMachineSessionControlView,
|
renderMachineSessionControlView,
|
||||||
} from "../../../runtime/ui/ini-panel/ui-shell.js";
|
} from "../../../runtime/ui/ini-panel/ui-shell.js";
|
||||||
|
|
||||||
assert.deepEqual(getIniPanelEntryManifest(), INI_PANEL_ENTRIES);
|
assert.deepEqual(getIniPanelEntryManifest(), INI_PANEL_ENTRIES);
|
||||||
|
assert.deepEqual(getVisibleIniPanelEntries(), INI_PANEL_ENTRIES);
|
||||||
|
assert.equal(controlPageStatusText({ lastAction: "x", runStatus: "ok", error: null }), "x | ok | -");
|
||||||
|
assert.deepEqual(createControlPageRefreshState({ phase: "waiting", statusText: "waiting for panel" }), {
|
||||||
|
phase: "waiting",
|
||||||
|
statusText: "waiting for panel",
|
||||||
|
view: null,
|
||||||
|
renderResult: 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 renderMachineSessionControlView, "function");
|
assert.equal(typeof renderMachineSessionControlView, "function");
|
||||||
|
|
||||||
const shellManifest = getIniPanelEntryManifest();
|
const shellManifest = getIniPanelEntryManifest();
|
||||||
shellManifest[0].title = "changed";
|
shellManifest[0].title = "changed";
|
||||||
assert.equal(INI_PANEL_ENTRIES[0].title, "Open edit and run panel");
|
assert.equal(INI_PANEL_ENTRIES[0].title, "Open edit and run panel");
|
||||||
|
const visibleEntries = getVisibleIniPanelEntries();
|
||||||
|
visibleEntries[0].title = "changed";
|
||||||
|
assert.equal(INI_PANEL_ENTRIES[0].title, "Open edit and run panel");
|
||||||
|
assert.deepEqual(createIniPanelLauncherLinkViewModel(), [
|
||||||
|
{
|
||||||
|
entryId: "edit-run",
|
||||||
|
href: "./index.html",
|
||||||
|
label: "Open edit and run panel",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
entryId: "control-view",
|
||||||
|
href: "./control-page.html",
|
||||||
|
label: "Open read-only control view",
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
const launcherLinks = createIniPanelLauncherLinkViewModel();
|
||||||
|
launcherLinks[0].label = "changed";
|
||||||
|
assert.equal(INI_PANEL_ENTRIES[0].title, "Open edit and run panel");
|
||||||
|
const shellViewModel = createIniPanelShellViewModel();
|
||||||
|
assert.deepEqual(shellViewModel.pages, [
|
||||||
|
{
|
||||||
|
id: "edit-run",
|
||||||
|
href: "./index.html",
|
||||||
|
title: "Open edit and run panel",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "control-view",
|
||||||
|
href: "./control-page.html",
|
||||||
|
title: "Open read-only control view",
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
assert.deepEqual(shellViewModel.launcherLinks, createIniPanelLauncherLinkViewModel());
|
||||||
|
assert.equal(shellViewModel.controlPage.createController, createMachineSessionControlPageController);
|
||||||
|
assert.equal(shellViewModel.controlPage.loadSessionState, loadMachineSessionForControlPageWorkflow);
|
||||||
|
assert.equal(shellViewModel.controlPage.refresh, refreshMachineSessionControlPage);
|
||||||
|
assert.equal(shellViewModel.controlPage.renderView, renderMachineSessionControlView);
|
||||||
|
shellViewModel.pages[0].title = "changed";
|
||||||
|
shellViewModel.launcherLinks[0].label = "changed";
|
||||||
|
assert.equal(INI_PANEL_ENTRIES[0].title, "Open edit and run panel");
|
||||||
|
|
||||||
const panelFrame = {
|
const panelFrame = {
|
||||||
contentWindow: {
|
contentWindow: {
|
||||||
@@ -48,5 +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");
|
||||||
|
|
||||||
console.log("ini_panel_ui_shell_node_smoke=ok");
|
console.log("ini_panel_ui_shell_node_smoke=ok");
|
||||||
|
|||||||
@@ -10,6 +10,8 @@ ROOT_DIR="$(cd "$(dirname "$0")/../../.." && pwd)"
|
|||||||
"$ROOT_DIR/tests/ui/node/verify_ini_panel_machine_file_summary.sh"
|
"$ROOT_DIR/tests/ui/node/verify_ini_panel_machine_file_summary.sh"
|
||||||
"$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_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"
|
||||||
|
|||||||
Reference in New Issue
Block a user